Skip to content

Commit 3746319

Browse files
Refactor: Modernize UI with View Binding and Edge-to-Edge
This commit introduces several modern Android development practices across the application. First, it refactors multiple Activities and Fragments to use View Binding instead of `findViewById`. This improves null safety and type safety when accessing views. Affected areas include `GridViewActivity`, `RadioButtonsActivity`, `AndroidStudioFragment`, `RoomActivity`, and others. Second, it enhances the native ad loading mechanism by dynamically resolving view bindings via reflection. This makes the `NativeAdLoader` more robust and less reliant on static view IDs. Third, Edge-to-Edge display is now consistently applied in `HelpActivity` and `SupportActivity` for a more immersive user interface. The general implementation in `BaseActivity` has been removed in favor of explicit calls in each relevant activity. Finally, this commit adds new string resources for the "Spinner" summary in multiple languages.
1 parent 8933a43 commit 3746319

File tree

49 files changed

+366
-142
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+366
-142
lines changed

app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/NativeAdLoader.java

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.d4rk.androidtutorials.java.ads.managers;
22

33
import android.content.Context;
4+
import android.content.res.Resources;
45
import android.text.TextUtils;
56
import android.util.Log;
67
import android.view.LayoutInflater;
@@ -12,6 +13,7 @@
1213

1314
import androidx.annotation.LayoutRes;
1415
import androidx.annotation.NonNull;
16+
import androidx.viewbinding.ViewBinding;
1517

1618
import com.d4rk.androidtutorials.java.R;
1719
import com.google.android.gms.ads.AdListener;
@@ -23,6 +25,10 @@
2325
import com.google.android.gms.ads.nativead.NativeAd;
2426
import com.google.android.gms.ads.nativead.NativeAdView;
2527

28+
import java.lang.reflect.Field;
29+
import java.lang.reflect.InvocationTargetException;
30+
import java.lang.reflect.Method;
31+
2632
/**
2733
* Helper class to load AdMob native ads into a container.
2834
*/
@@ -73,7 +79,7 @@ public static void load(@NonNull Context context,
7379
adView.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
7480
container.getPaddingRight(), container.getPaddingBottom());
7581
container.setPadding(0, 0, 0, 0);
76-
populateNativeAdView(nativeAd, adView);
82+
populateNativeAdView(nativeAd, adView, layoutRes);
7783
container.removeAllViews();
7884
container.addView(adView);
7985
container.requestLayout();
@@ -92,14 +98,22 @@ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
9298
adLoader.loadAd(adRequest);
9399
}
94100

95-
private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull NativeAdView adView) {
96-
MediaView mediaView = adView.findViewById(R.id.ad_media);
97-
TextView headlineView = adView.findViewById(R.id.ad_headline);
98-
TextView bodyView = adView.findViewById(R.id.ad_body);
99-
Button callToActionView = adView.findViewById(R.id.ad_call_to_action);
100-
ImageView iconView = adView.findViewById(R.id.ad_app_icon);
101-
TextView attributionView = adView.findViewById(R.id.ad_attribution);
102-
AdChoicesView adChoicesView = adView.findViewById(R.id.ad_choices);
101+
private static void populateNativeAdView(@NonNull NativeAd nativeAd,
102+
@NonNull NativeAdView adView,
103+
@LayoutRes int layoutRes) {
104+
ViewBinding binding = tryBind(adView, layoutRes);
105+
if (binding == null) {
106+
Log.w(TAG, "Could not bind native ad view for layout " + layoutRes);
107+
return;
108+
}
109+
110+
MediaView mediaView = getBindingField(binding, "adMedia", MediaView.class);
111+
TextView headlineView = getBindingField(binding, "adHeadline", TextView.class);
112+
TextView bodyView = getBindingField(binding, "adBody", TextView.class);
113+
Button callToActionView = getBindingField(binding, "adCallToAction", Button.class);
114+
ImageView iconView = getBindingField(binding, "adAppIcon", ImageView.class);
115+
TextView attributionView = getBindingField(binding, "adAttribution", TextView.class);
116+
AdChoicesView adChoicesView = getBindingField(binding, "adChoices", AdChoicesView.class);
103117

104118
if (mediaView != null) {
105119
adView.setMediaView(mediaView);
@@ -166,4 +180,53 @@ private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull Na
166180

167181
adView.setNativeAd(nativeAd);
168182
}
183+
184+
@androidx.annotation.Nullable
185+
private static ViewBinding tryBind(@NonNull NativeAdView adView, @LayoutRes int layoutRes) {
186+
try {
187+
String resourceName = adView.getResources().getResourceEntryName(layoutRes);
188+
String bindingName = toBindingClassName(resourceName);
189+
String fullClassName = adView.getContext().getPackageName() + ".databinding." + bindingName;
190+
Class<?> bindingClass = Class.forName(fullClassName);
191+
Method bindMethod = bindingClass.getMethod("bind", View.class);
192+
return (ViewBinding) bindMethod.invoke(null, adView);
193+
} catch (Resources.NotFoundException | ClassNotFoundException | NoSuchMethodException |
194+
IllegalAccessException | InvocationTargetException e) {
195+
Log.w(TAG, "Failed to create view binding for native ad layout", e);
196+
return null;
197+
}
198+
}
199+
200+
@androidx.annotation.Nullable
201+
private static <T> T getBindingField(@NonNull ViewBinding binding,
202+
@NonNull String fieldName,
203+
@NonNull Class<T> type) {
204+
try {
205+
Field field = binding.getClass().getField(fieldName);
206+
Object value = field.get(binding);
207+
if (type.isInstance(value)) {
208+
return type.cast(value);
209+
}
210+
} catch (NoSuchFieldException | IllegalAccessException e) {
211+
Log.w(TAG, "Unable to access binding field " + fieldName, e);
212+
}
213+
return null;
214+
}
215+
216+
@NonNull
217+
private static String toBindingClassName(@NonNull String resourceName) {
218+
StringBuilder builder = new StringBuilder(resourceName.length());
219+
boolean capitalize = true;
220+
for (int i = 0; i < resourceName.length(); i++) {
221+
char c = resourceName.charAt(i);
222+
if (c == '_') {
223+
capitalize = true;
224+
} else {
225+
builder.append(capitalize ? Character.toUpperCase(c) : c);
226+
capitalize = false;
227+
}
228+
}
229+
builder.append("Binding");
230+
return builder.toString();
231+
}
169232
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/components/NoCodeAdFragment.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import androidx.fragment.app.Fragment;
1111
import androidx.viewbinding.ViewBinding;
1212

13-
import com.d4rk.androidtutorials.java.R;
1413
import com.d4rk.androidtutorials.java.ads.AdUtils;
1514

1615
/**
@@ -35,7 +34,7 @@ public abstract class NoCodeAdFragment<T extends ViewBinding> extends Fragment {
3534
* Called after the binding has been created and the banner ad loaded.
3635
* Subclasses can override to perform additional setup.
3736
*
38-
* @param binding The binding instance.
37+
* @param binding The binding instance.
3938
* @param savedInstanceState Saved instance state.
4039
*/
4140
protected void onBindingCreated(@NonNull T binding, @Nullable Bundle savedInstanceState) {
@@ -47,12 +46,14 @@ protected void onBindingCreated(@NonNull T binding, @Nullable Bundle savedInstan
4746
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
4847
@Nullable Bundle savedInstanceState) {
4948
binding = inflateBinding(inflater, container);
50-
View adView = binding.getRoot().findViewById(R.id.ad_view);
51-
AdUtils.loadBanner(adView);
49+
AdUtils.loadBanner(getAdView(binding));
5250
onBindingCreated(binding, savedInstanceState);
5351
return binding.getRoot();
5452
}
5553

54+
@NonNull
55+
protected abstract View getAdView(@NonNull T binding);
56+
5657
@Override
5758
public void onDestroyView() {
5859
super.onDestroyView();

app/src/main/java/com/d4rk/androidtutorials/java/ui/components/navigation/BaseActivity.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,17 @@
33
import android.annotation.SuppressLint;
44
import android.os.Bundle;
55
import android.view.Menu;
6-
import android.view.View;
76

87
import androidx.annotation.Nullable;
98
import androidx.appcompat.app.ActionBar;
109
import androidx.appcompat.app.AppCompatActivity;
1110
import androidx.appcompat.view.menu.MenuBuilder;
1211

13-
import com.d4rk.androidtutorials.java.R;
14-
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
15-
1612
public abstract class BaseActivity extends AppCompatActivity {
1713

1814
@Override
1915
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
2016
super.onPostCreate(savedInstanceState);
21-
View container = findViewById(R.id.container);
22-
if (container != null) {
23-
EdgeToEdgeDelegate.apply(this, container);
24-
}
2517
ActionBar actionBar = getSupportActionBar();
2618
if (actionBar != null) {
2719
actionBar.setDisplayHomeAsUpEnabled(true);

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/AndroidStudioFragment.java

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@
3030
import com.d4rk.androidtutorials.java.ads.AdUtils;
3131
import com.d4rk.androidtutorials.java.ads.views.NativeAdBannerView;
3232
import com.d4rk.androidtutorials.java.databinding.FragmentAndroidStudioBinding;
33+
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceBinding;
34+
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceCategoryBinding;
35+
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceWidgetOpenInNewBinding;
3336
import com.google.android.gms.ads.AdListener;
3437
import com.google.android.gms.ads.LoadAdError;
3538
import com.google.android.material.button.MaterialButton;
36-
import com.google.android.material.card.MaterialCardView;
37-
import com.google.android.material.imageview.ShapeableImageView;
3839
import com.google.android.material.shape.CornerFamily;
3940
import com.google.android.material.shape.ShapeAppearanceModel;
40-
import com.google.android.material.textview.MaterialTextView;
4141

4242
import org.xmlpull.v1.XmlPullParser;
4343
import org.xmlpull.v1.XmlPullParserException;
@@ -375,13 +375,19 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
375375
adView.setNativeAdUnitId(R.string.native_ad_lessons_list_unit_id);
376376
return new AdHolder(adView);
377377
} else if (viewType == TYPE_CATEGORY) {
378-
View view = LayoutInflater.from(parent.getContext())
379-
.inflate(R.layout.item_preference_category, parent, false);
380-
return new CategoryHolder(view);
378+
ItemPreferenceCategoryBinding binding = ItemPreferenceCategoryBinding.inflate(
379+
LayoutInflater.from(parent.getContext()),
380+
parent,
381+
false
382+
);
383+
return new CategoryHolder(binding);
381384
} else {
382-
View view = LayoutInflater.from(parent.getContext())
383-
.inflate(R.layout.item_preference, parent, false);
384-
return new LessonHolder(view);
385+
ItemPreferenceBinding binding = ItemPreferenceBinding.inflate(
386+
LayoutInflater.from(parent.getContext()),
387+
parent,
388+
false
389+
);
390+
return new LessonHolder(binding);
385391
}
386392
}
387393

@@ -424,40 +430,36 @@ static class AdHolder extends RecyclerView.ViewHolder {
424430
}
425431

426432
static class LessonHolder extends RecyclerView.ViewHolder {
427-
final MaterialCardView card;
428-
final ShapeableImageView icon;
429-
final MaterialTextView title;
430-
final MaterialTextView summary;
431-
final FrameLayout widgetFrame;
432-
final MaterialButton externalButton;
433-
434-
LessonHolder(@NonNull View itemView) {
435-
super(itemView);
436-
card = (MaterialCardView) itemView;
437-
icon = itemView.findViewById(android.R.id.icon);
438-
title = itemView.findViewById(android.R.id.title);
439-
summary = itemView.findViewById(android.R.id.summary);
440-
widgetFrame = itemView.findViewById(android.R.id.widget_frame);
441-
LayoutInflater.from(itemView.getContext())
442-
.inflate(R.layout.item_preference_widget_open_in_new, widgetFrame, true);
443-
externalButton = widgetFrame.findViewById(R.id.open_in_new);
433+
private final ItemPreferenceBinding binding;
434+
private final ItemPreferenceWidgetOpenInNewBinding widgetBinding;
435+
436+
LessonHolder(@NonNull ItemPreferenceBinding binding) {
437+
super(binding.getRoot());
438+
this.binding = binding;
439+
widgetBinding = ItemPreferenceWidgetOpenInNewBinding.inflate(
440+
LayoutInflater.from(binding.getRoot().getContext()),
441+
binding.widgetFrame,
442+
true
443+
);
444444
}
445445

446446
void bind(Lesson lesson, boolean first, boolean last) {
447447
if (lesson.iconRes != 0) {
448-
icon.setImageResource(lesson.iconRes);
449-
icon.setVisibility(View.VISIBLE);
448+
binding.icon.setImageResource(lesson.iconRes);
449+
binding.icon.setVisibility(View.VISIBLE);
450450
} else {
451-
icon.setVisibility(View.GONE);
451+
binding.icon.setVisibility(View.GONE);
452452
}
453-
title.setText(lesson.title);
453+
binding.title.setText(lesson.title);
454454
if (lesson.summary != null) {
455-
summary.setText(lesson.summary);
456-
summary.setVisibility(View.VISIBLE);
455+
binding.summary.setText(lesson.summary);
456+
binding.summary.setVisibility(View.VISIBLE);
457457
} else {
458-
summary.setVisibility(View.GONE);
458+
binding.summary.setVisibility(View.GONE);
459459
}
460460
boolean showExternalButton = lesson.opensInBrowser && lesson.intent != null;
461+
FrameLayout widgetFrame = binding.widgetFrame;
462+
MaterialButton externalButton = widgetBinding.openInNew;
461463
widgetFrame.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
462464
externalButton.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
463465
externalButton.setEnabled(showExternalButton);
@@ -486,30 +488,30 @@ private void applyCorners(boolean first, boolean last) {
486488
itemView.getResources().getDisplayMetrics());
487489
float dp24 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24,
488490
itemView.getResources().getDisplayMetrics());
489-
ShapeAppearanceModel.Builder builder = card.getShapeAppearanceModel().toBuilder()
491+
ShapeAppearanceModel.Builder builder = binding.lessonCard.getShapeAppearanceModel().toBuilder()
490492
.setTopLeftCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
491493
.setTopRightCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
492494
.setBottomLeftCorner(CornerFamily.ROUNDED, last ? dp24 : dp4)
493495
.setBottomRightCorner(CornerFamily.ROUNDED, last ? dp24 : dp4);
494-
card.setShapeAppearanceModel(builder.build());
496+
binding.lessonCard.setShapeAppearanceModel(builder.build());
495497
}
496498
}
497499

498500
static class CategoryHolder extends RecyclerView.ViewHolder {
499-
final MaterialTextView title;
501+
private final ItemPreferenceCategoryBinding binding;
500502

501-
CategoryHolder(@NonNull View itemView) {
502-
super(itemView);
503-
title = itemView.findViewById(android.R.id.title);
503+
CategoryHolder(@NonNull ItemPreferenceCategoryBinding binding) {
504+
super(binding.getRoot());
505+
this.binding = binding;
504506
}
505507

506508
void bind(Category category) {
507509
if (category.iconRes != 0) {
508-
title.setCompoundDrawablesRelativeWithIntrinsicBounds(category.iconRes, 0, 0, 0);
510+
binding.title.setCompoundDrawablesRelativeWithIntrinsicBounds(category.iconRes, 0, 0, 0);
509511
} else {
510-
title.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
512+
binding.title.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
511513
}
512-
title.setText(category.title);
514+
binding.title.setText(category.title);
513515
}
514516
}
515517
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/basics/sdk/AndroidSDK.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import android.widget.TableRow;
88
import android.widget.TextView;
99

10-
import com.d4rk.androidtutorials.java.R;
1110
import com.d4rk.androidtutorials.java.ads.AdUtils;
1211
import com.d4rk.androidtutorials.java.data.model.AndroidVersion;
1312
import com.d4rk.androidtutorials.java.databinding.ActivityAndroidSdkBinding;
@@ -64,7 +63,8 @@ public class AndroidSDK extends UpNavigationActivity {
6463
protected void onCreate(Bundle savedInstanceState) {
6564
super.onCreate(savedInstanceState);
6665
binding = ActivityAndroidSdkBinding.inflate(getLayoutInflater());
67-
setContentView(binding.getRoot()); EdgeToEdgeDelegate.apply(this, binding.scrollView);
66+
setContentView(binding.getRoot());
67+
EdgeToEdgeDelegate.apply(this, binding.scrollView);
6868

6969
AdUtils.loadBanner(binding.adViewBottom);
7070
AdUtils.loadBanner(binding.adView);
@@ -74,7 +74,7 @@ protected void onCreate(Bundle savedInstanceState) {
7474
}
7575

7676
private void createDynamicTable() {
77-
TableLayout tableLayout = binding.cardViewTableLayout.findViewById(R.id.table_layout);
77+
TableLayout tableLayout = binding.tableLayout;
7878
for (AndroidVersion version : androidVersions) {
7979
TableRow row = new TableRow(this);
8080

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/basics/shortcuts/tabs/DebuggingShortcutsActivity.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ protected void onCreate(Bundle savedInstanceState) {
1717
setContentView(binding.getRoot());
1818

1919

20-
EdgeToEdgeDelegate.apply(this, binding.scrollView); AdUtils.loadBanner(binding.adView);
20+
EdgeToEdgeDelegate.apply(this, binding.scrollView);
21+
AdUtils.loadBanner(binding.adView);
2122
new FastScrollerBuilder(binding.scrollView).useMd2Style().build();
2223
}
2324
}

0 commit comments

Comments
 (0)