גרסה זו מיועדת לפרויקט שכבר משתמש ב-View Binding. בסוף השיעור MenuActivity תיפתח רק לאחר התחברות מוצלחת ל-Firebase.
הנחות מוקדמות
- השלמתם את
018aואת שיעורי ה-View Binding. - קיבלתם מהמורה עותק של
google-services.jsonשמתאים לחבילהcom.example.tictacmenuושמרתם אותו בתיקייהapp. - ספק ההתחברות Email/Password כבר מופעל בפרויקט Firebase וקיים משתמש לבדיקה.
אין צורך ליצור בשיעור זה פרויקט Firebase חדש. אם מתקבלת השגיאה No matching client found for package name, קובץ ה-JSON אינו מתאים ל-applicationId של האפליקציה.
שלב 1 - חיבור קצר של Firebase ב-Gradle
1.1 עדכון gradle/libs.versions.toml
יש להוסיף כל פריט למקום המתאים לו בקטלוג:
[versions]
+googleServices = "4.4.1"
+firebaseBom = "34.16.0"
[libraries]
+firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
+firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
+firebase-database = { group = "com.google.firebase", name = "firebase-database" }
[plugins]
+google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
[versions] מכיל מספרי גרסאות, [libraries] מכיל תלויות, ו-[plugins] מכיל תוספי Gradle. ה-BoM, Auth ו-Database הם ספריות ולא plugins.
1.2 עדכון app/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
+ alias(libs.plugins.google.services)
}
dependencies {
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.auth)
+ implementation(libs.firebase.database)
}
בצעו Gradle Sync. ה-BoM קובע את הגרסאות התואמות של Auth ושל Database, ולכן הן אינן מקבלות גרסה נפרדת ב-TOML.
שלב 2 - יצירת FBRef
צרו את הקובץ:
app/src/main/java/com/example/tictacmenu/services/FBRef.java
package com.example.tictacmenu.services;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
public final class FBRef {
public static final FirebaseAuth refAuth = FirebaseAuth.getInstance();
public static final FirebaseDatabase FBDB = FirebaseDatabase.getInstance();
public static final DatabaseReference refUsers = FBDB.getReference("Users");
public static String uid;
public static DatabaseReference refUser;
private FBRef() { }
public static void getUser(FirebaseUser fbuser) {
if (fbuser == null) {
uid = null;
refUser = null;
return;
}
uid = fbuser.getUid();
refUser = refUsers.child(uid);
}
}
שלב 3 - החלפת המעבר המושהה בהתחברות אמיתית
ערכו את:
app/src/main/java/com/example/tictacmenu/activities/LoginActivity.java
ה-diff הבא מבוסס על השינוי האמיתי ב-TicTacMenu. הוא ממשיך להשתמש ב-ActivityLoginBinding, ולכן אין צורך ב-findViewById או בשדות נפרדים לכל רכיב במסך.
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
+import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;
import com.example.tictacmenu.databinding.ActivityLoginBinding;
+import com.example.tictacmenu.services.FBRef;
import com.example.tictacmenu.shell.MenuActivity;
+import com.google.firebase.auth.FirebaseUser;
public class LoginActivity extends AppCompatActivity {
- private static final long LOGIN_SHELL_DELAY_MS = 4000;
private static final String PREFS_NAME = "PREFS_NAME";
private static final String KEY_STAY_CONNECT = "stayConnect";
private ActivityLoginBinding binding;
private SharedPreferences settings;
- private final Handler handler = new Handler(Looper.getMainLooper());
+ private boolean loginInProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
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;
});
settings = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
binding.cBstayconnect.setChecked(settings.getBoolean(KEY_STAY_CONNECT, false));
binding.cBstayconnect.setOnCheckedChangeListener((buttonView, isChecked) ->
settings.edit().putBoolean(KEY_STAY_CONNECT, isChecked).apply()
);
- handler.postDelayed(() -> {
- settings.edit().putBoolean(KEY_STAY_CONNECT, binding.cBstayconnect.isChecked()).apply();
- startActivity(new Intent(LoginActivity.this, MenuActivity.class));
- finish();
- }, LOGIN_SHELL_DELAY_MS);
+ setLoginInProgress(false);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ boolean stayConnected = settings.getBoolean(KEY_STAY_CONNECT, false);
+ FirebaseUser currentUser = FBRef.refAuth.getCurrentUser();
+ if (currentUser != null && stayConnected) {
+ FBRef.getUser(currentUser);
+ openMenu();
+ }
}
public void onLoginClick(View view) {
- Toast.makeText(this, "Email/password auth is added in a later lesson", Toast.LENGTH_SHORT).show();
+ 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);
+ FBRef.refAuth.signInWithEmailAndPassword(email, password)
+ .addOnCompleteListener(this, task -> {
+ setLoginInProgress(false);
+ if (task.isSuccessful()) {
+ FirebaseUser user = FBRef.refAuth.getCurrentUser();
+ if (user == null) {
+ Toast.makeText(this, "Login succeeded but no user was returned", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ FBRef.getUser(user);
+ settings.edit().putBoolean(KEY_STAY_CONNECT,
+ binding.cBstayconnect.isChecked()).apply();
+ Toast.makeText(this, "Login success", Toast.LENGTH_SHORT).show();
+ openMenu();
+ return;
+ }
+ String errorMessage = (task.getException() != null)
+ ? task.getException().getLocalizedMessage()
+ : "Login failed";
+ Toast.makeText(this, "Login failed: " + errorMessage, Toast.LENGTH_LONG).show();
+ });
+ }
+
+ private void setLoginInProgress(boolean inProgress) {
+ loginInProgress = inProgress;
+ binding.btnLogin.setEnabled(!inProgress);
+ binding.btnLogin.setText(inProgress ? "Logging in..." : "Login");
+ }
+
+ private void openMenu() {
+ startActivity(new Intent(this, MenuActivity.class));
+ finish();
}
public void onGoogleLoginClick(View view) {
- Toast.makeText(this, "Google sign-in is an advanced later phase", Toast.LENGTH_SHORT).show();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- handler.removeCallbacksAndMessages(null);
+ Toast.makeText(this, "Google sign-in is added in 018d (requires SHA1)", Toast.LENGTH_SHORT).show();
}
}
מה השתנה?
- הוסרו ה-
Handlerוהמעבר האוטומטי לאחר ארבע שניות. - הערכים נקראים ישירות דרך
binding.eTemailו-binding.eTpass. - הכפתור מושבת בזמן שההתחברות מתבצעת, כדי למנוע לחיצות כפולות.
- רק הצלחה של
signInWithEmailAndPassword(...)פותחת אתMenuActivity. - אם סומן “הישאר מחובר”, משתמש קיים מועבר לתפריט מתוך
onStart().
שלב 4 - בדיקה
הריצו את האפליקציה. הכניסו שם משתמש וסיסמה שהמורה ייתן לכם, ובדקו שקיבלתם הודעה שהלוגין הצליח.
בסיום השיעור ההתחברות במייל ובסיסמה פעילה, FBRef מחזיק את המשתמש הנוכחי, וכל הגישה לרכיבי המסך נעשית באמצעות View Binding.