Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 MaterialTextViewtitle;
501+
privatefinal ItemPreferenceCategoryBindingbinding;
500502

501-
CategoryHolder(@NonNull ViewitemView) {
502-
super(itemView);
503-
title = itemView.findViewById(android.R.id.title);
503+
CategoryHolder(@NonNull ItemPreferenceCategoryBindingbinding) {
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)

AltStyle によって変換されたページ (->オリジナル) /