חזרה ל־018d: התחברות Google והגדרת SHA1
המטרה בשלב הזה היא לא לכתוב מחדש את כל האפליקציה. מפעילים View Binding, מסנכרנים Gradle, ואז מחליפים בהדרגה קריאות findViewById בגישה ישירה דרך אובייקט binding.
בסוף השלב הזה נרצה לראות:
viewBinding = trueמוגדר במודולapp.- Android Studio מזהה מחלקות כמו
ActivityLoginBinding. - לפחות מסך אחד, למשל
LoginActivity, כבר עובד בליfindViewById. - שאר המסכים נשארים תקינים ואפשר להמיר אותם בהמשך בקצב מבוקר.
שלב 1 - הפעלת View Binding ב־Gradle
פתחו את קובץ המודול:
app/build.gradle.kts
בתוך הבלוק android, הוסיפו:
android {
namespace = "com.example.tictacmenu"
compileSdk {
version = release(36)
}
+ buildFeatures {
+ viewBinding = true
+ }
+
defaultConfig {
applicationId = "com.example.tictacmenu"
...
}
}
אם כבר קיים אצלכם בלוק buildFeatures, אל תיצרו בלוק שני. הוסיפו רק את השורה:
viewBinding = true
אחרי העריכה חובה לבצע Sync Now / Sync Project with Gradle Files. רק אחרי הסנכרון Android Studio יוצר את מחלקות ה־Binding.
אם ActivityLoginBinding מופיע באדום מיד אחרי העריכה, זה בדרך כלל לא אומר שהקוד שגוי. קודם מריצים Gradle Sync. אם זה עדיין אדום, בודקים ששם קובץ ה־XML באמת activity_login.xml ושאתם מייבאים את ה־package הנכון.
שלב 2 - להבין את השמות שנוצרים
View Binding יוצר מחלקת Java עבור כל קובץ layout תחת res/layout.
דוגמאות נפוצות:
| קובץ XML | מחלקת Binding |
|---|---|
activity_login.xml |
ActivityLoginBinding |
activity_menu.xml |
ActivityMenuBinding |
fragment_home.xml |
FragmentHomeBinding |
גם מזהי ה־Views הופכים לשדות על binding.
| id ב־XML | גישה בקוד |
|---|---|
@+id/main |
binding.main |
@+id/cBstayconnect |
binding.cBstayconnect |
@+id/drawer_layout |
binding.drawerLayout |
@+id/navigation_view |
binding.navigationView |
ב־Java השמות נוצרים לפי ה־id. אם משנים id ב־XML, הקוד שמשתמש בשם הישן יישבר בזמן קומפילציה. זה טוב: השגיאה מתגלה מוקדם במקום להפוך לקריסה בזמן ריצה.
שלב 3 - המרה ראשונה: LoginActivity
פתחו ב־Android Studio את app > kotlin+java > com.example.tictacmenu > activities > LoginActivity.
נקודת המוצא כאן היא מסך ההתחברות אחרי 018c ו־018d: דוא״ל וסיסמה, Google ושמירת הבחירה ״הישאר מחובר״. אם כבר השתמשתם בגרסת 018c עם binding, בצעו רק את ההחלפות שעדיין חסרות. אין להחזיר את המעבר הזמני עם Handler שהוסר בשיעור ההתחברות.
א. שדה binding ואתחול המסך
הוסיפו את הייבוא (התאימו את ה־package לפרויקט שלכם):
import com.example.tictacmenu.databinding.ActivityLoginBinding;
בגוף המחלקה, מחוץ לכל מתודה, החליפו את שדות רכיבי המסך בשדה binding אחד. שדות שאינם רכיבי מסך, כמו settings ו־loginInProgress, נשארים:
public class LoginActivity extends AppCompatActivity {
⁞
- private CheckBox cBstayconnect;
- private EditText eTemail;
- private EditText eTpass;
- private Button btnLogin;
- private Button btnGoogleSignIn;
+ private ActivityLoginBinding binding;
⁞
בתחילת onCreate, מיד אחרי EdgeToEdge.enable(this):
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
- setContentView(R.layout.activity_login);
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ binding = ActivityLoginBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
מחקו את ההשמות הבאות מהמקום שבו הן נמצאות אצלכם — initViews() או onCreate:
- cBstayconnect = findViewById(R.id.cBstayconnect);
- eTemail = findViewById(R.id.eTemail);
- eTpass = findViewById(R.id.eTpass);
- btnLogin = findViewById(R.id.btnLogin);
- btnGoogleSignIn = findViewById(R.id.btnGoogleSignIn);
אם initViews() התרוקנה, מחקו גם אותה ואת הקריאה אליה מתוך onCreate. השאירו כל פעולה אחרת, ובפרט את FBRef.initializeGoogleSignIn(this).
ב. מחליפים כל שימוש בשדות שמחקנו
מחיקת ההצהרות ו־findViewById היא רק חצי מההמרה. בכל המחלקה, גם במתודות אחרות ובתוך callbacks, כל שימוש בשדה UI שמחקנו צריך לעבור דרך binding.. אחרת יישארו שמות לא מוכרים כמו eTemail או btnLogin.
binding.eTemail הוא אותו רכיב קלט שאליו ניגשנו קודם דרך המשתנה eTemail. הפעולות getText(), setError() ו־requestFocus() נשארות; משתנה רק הדרך להגיע לרכיב. שם השדה נוצר לפי ה־id ב־XML ואפשר לבחור אותו בהשלמה האוטומטית אחרי binding..
בהשוואות הבאות הקוד לפני מופיע משמאל והקוד אחרי מימין. התצוגה מיועדת למסך רחב ושומרת על השורות זו מול זו. רק התוספת binding. מסומנת בירוק ובקו תחתון. שאר השורה נשארת ללא הדגשה, כי אין בה שינוי.
ב־onLoginClick: קריאת הקלט, הודעות השגיאה והעברת המיקוד. החליפו את תחילת המתודה והשאירו את המשך ההתחברות במקומו:
לפני
public void onLoginClick(View view) {
if (loginInProgress) return;
String email = eTemail.getText().toString().trim();
String password = eTpass.getText().toString();
if (TextUtils.isEmpty(email)) {
eTemail.setError("Enter e-mail");
eTemail.requestFocus();
return;
}
if (TextUtils.isEmpty(password)) {
eTpass.setError("Enter password");
eTpass.requestFocus();
return;
}
setLoginInProgress(true);
⁞אחרי
public void onLoginClick(View view) {
if (loginInProgress) return;
String email = binding.eTemail.getText().toString().trim();
String password = binding.eTpass.getText().toString();
if (TextUtils.isEmpty(email)) {
binding.eTemail.setError("Enter e-mail");
binding.eTemail.requestFocus();
return;
}
if (TextUtils.isEmpty(password)) {
binding.eTpass.setError("Enter password");
binding.eTpass.requestFocus();
return;
}
setLoginInProgress(true);
⁞email ו־password נשארים משתני String מקומיים, ו־loginInProgress נשאר שדה boolean. לא מוסיפים להם binding.. גם הפרמטר view של מתודת הלחיצה נשאר כפי שהוא.
ב־onCreate: שחזור הבחירה והאזנה לשינוי ב־checkbox.
לפני
cBstayconnect.setChecked(settings.getBoolean(KEY_STAY_CONNECT, false));
cBstayconnect.setOnCheckedChangeListener((buttonView, isChecked) ->
settings.edit().putBoolean(KEY_STAY_CONNECT, isChecked).apply()
);אחרי
binding.cBstayconnect.setChecked(settings.getBoolean(KEY_STAY_CONNECT, false));
binding.cBstayconnect.setOnCheckedChangeListener((buttonView, isChecked) ->
settings.edit().putBoolean(KEY_STAY_CONNECT, isChecked).apply()
);בתוך callback של הצלחת ההתחברות ב־onLoginClick:
לפני
FBRef.getUser(user);
settings.edit().putBoolean(KEY_STAY_CONNECT, cBstayconnect.isChecked()).apply();
Toast.makeText(this, "Login success", Toast.LENGTH_SHORT).show();אחרי
FBRef.getUser(user);
settings.edit().putBoolean(KEY_STAY_CONNECT, binding.cBstayconnect.isChecked()).apply();
Toast.makeText(this, "Login success", Toast.LENGTH_SHORT).show();גם בתוך callback של הצלחת ההתחברות ב־firebaseAuthWithGoogle:
לפני
FBRef.getUser(currentUser);
settings.edit().putBoolean(KEY_STAY_CONNECT, cBstayconnect.isChecked()).apply();
Toast.makeText(this, "Google login success", Toast.LENGTH_SHORT).show();אחרי
FBRef.getUser(currentUser);
settings.edit().putBoolean(KEY_STAY_CONNECT, binding.cBstayconnect.isChecked()).apply();
Toast.makeText(this, "Google login success", Toast.LENGTH_SHORT).show();ב־setLoginInProgress: הפעלת הכפתורים ועדכון הכיתוב.
לפני
private void setLoginInProgress(boolean inProgress) {
loginInProgress = inProgress;
btnLogin.setEnabled(!inProgress);
btnGoogleSignIn.setEnabled(!inProgress);
btnLogin.setText(inProgress ? "Logging in..." : "Login");
}אחרי
private void setLoginInProgress(boolean inProgress) {
loginInProgress = inProgress;
binding.btnLogin.setEnabled(!inProgress);
binding.btnGoogleSignIn.setEnabled(!inProgress);
binding.btnLogin.setText(inProgress ? "Logging in..." : "Login");
}אם טרם הוספתם את התחברות Google, המירו את השורות שכבר קיימות אצלכם; אין צורך להוסיף כאן את מנגנון ההתחברות שלה.
ג. בדיקת השלמת ההמרה
- חפשו בקובץ את
cBstayconnect,eTemail,eTpass,btnLoginו־btnGoogleSignIn. בכל שימוש ברכיבים שהשדות שלהם נמחקו צריכה להיות גישה דרךbinding., גם מחוץ ל־onCreate. אל תבצעו החלפת טקסט גורפת שעלולה ליצורbinding.binding.eTemail. - ודאו שאין עוד
findViewByIdבמסך הזה או קריאה למתודתinitViewsשנמחקה. הסירו imports שלCheckBox,EditTextו־Buttonרק אם אין בהם עוד שימוש. הסירו את import שלRרק אם אין בקובץ שימוש אחר במשאבים דרךR. - הריצו
Build. אם שם UI ישן מופיע באדום, השלימו את ההמרה באותו מקום. - הריצו את האפליקציה ולחצו Login עם דוא״ל ריק, ואחר כך עם דוא״ל וסיסמה ריקה. ודאו שהשגיאה והמיקוד מופיעים בשדה הנכון.
- בדקו התחברות בדוא״ל וב־Google אם הוגדרה בפרויקט. ודאו ששני הכפתורים מושבתים בזמן ההתחברות וחוזרים לפעול בסיומה, ושהכיתוב של Login מתעדכן.
- בדקו שהבחירה ״הישאר מחובר״ נשמרת לאחר סגירה ופתיחה מחדש, ושהתחברות מוצלחת עדיין פותחת את התפריט.
מקור ההשוואה בהיסטוריית TicTacMenu
הקומיט 1358935 מציג את המרת מסך ה־checkbox המוקדם ל־binding. בקומיט e14c7c2 נוסף קוד ההתחברות בדוא״ל כשהוא כבר משתמש ב־binding, וב־fd354b7 נוספה התחברות Google. לכן הדיפים כאן משווים את הקוד ללא binding שנלמד ב־018c/018d לצורת הגישה שבפרויקט הייחוס; הם אינם העתק של קומיט יחיד שממיר את כל מסך ההתחברות.

שלב 4 - איך ממשיכים למסכים אחרים
אל תנסו להמיר את כל הפרויקט במכה אחת. בחרו Activity אחד, בנו והריצו, ואז עברו לבא.
הדפוס הקבוע בכל Activity הוא:
-setContentView(R.layout.activity_something);
+binding = ActivitySomethingBinding.inflate(getLayoutInflater());
+setContentView(binding.getRoot());
ואז מחליפים:
-drawerLayout = findViewById(R.id.drawer_layout);
-MaterialToolbar toolbar = findViewById(R.id.toolbar);
-NavigationView navigationView = findViewById(R.id.navigation_view);
+drawerLayout = binding.drawerLayout;
+MaterialToolbar toolbar = binding.toolbar;
+NavigationView navigationView = binding.navigationView;
במסך המשחק אפשר להמיר בהדרגה. לדוגמה, במקום:
-EditText ipEdit = findViewById(R.id.editServerIP);
+EditText ipEdit = binding.editServerIP;
אם יש לולאות שמקבלות id ומבצעות findViewById(id), מותר להשאיר אותן לשלב מאוחר יותר. המטרה כאן היא מעבר מדורג, לא ריפקטור גדול.
נוחות שנרוויח בהמשך
כשנתחיל להוסיף יותר UI, Firebase, תפריטים, ו־Fragments, binding יחסוך הרבה עבודה שחוזרת על עצמה:
- Autocomplete אמיתי: כותבים
binding.ורואים את כל ה־Views של המסך. - פחות casting: אין צורך להמיר ידנית
Viewל־Button,EditText,NavigationViewוכדומה. - שגיאות מוקדמות: אם מחקתם או שיניתם id ב־XML, הקוד נשבר בקומפילציה ולא בזמן ריצה.
- מסכי Login נוחים יותר: כפי שראינו בהמרת מסך ההתחברות, אפשר לקרוא
binding.eTemail,binding.eTpass,binding.btnGoogleSignInבלי לחפש אותם ידנית. - Drawer נקי יותר:
MenuActivityנהיה קריא יותר עםbinding.drawerLayout,binding.toolbar, ו־binding.navigationView. - Fragments בטוחים יותר: גם בפרגמנטים משתמשים ב־binding, אבל שם חייבים לנקות אותו ב־
onDestroyView.
דפוס קצר ל־Fragment
ב־Fragment ה־View יכול להיהרס לפני שה־Fragment עצמו נהרס. לכן לא משאירים binding חי אחרי onDestroyView.
private FragmentHomeBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
תקלות נפוצות
ActivityLoginBinding לא מזוהה:
- ודאו שביצעתם Gradle Sync אחרי
viewBinding = true. - ודאו שהקובץ נמצא תחת
app/src/main/res/layout/activity_login.xml. - ודאו שהייבוא הוא
com.example.tictacmenu.databinding.ActivityLoginBindingאו ה־package שלכם.
שדה מסוים לא קיים על binding:
- בדקו את ה־id המדויק ב־XML.
- אם ה־id כולל underscore, השם בקוד יהיה בדרך כלל camelCase, למשל
drawer_layout->drawerLayout. - אם הרכיב נמצא רק בגרסת layout מסוימת, למשל רק ב־
layout-land, ייתכן שהשדה יהיה nullable ותצטרכו לבדוקnull.
אפשר לגרום ל־View Binding להתעלם מקובץ layout מסוים בעזרת tools:viewBindingIgnore="true" על ה־root של ה־XML, אבל בשלב הזה בדרך כלל אין צורך בזה.
המשך שיעורי View Binding
מורה־עזר ב־Gemini
פתחו את Gem: מעבדת TicTacToe: Model, View Binding ועיצוב כדי לקבל רמזים, שאלות אבחון והסברים המתאימים לשלב שבו אתם נמצאים.