בפרק הזה נתחיל מפרויקט Empty Views Activity רגיל ונבצע עליו רק שינויים
הכרחיים. בסוף הפרק נגדיר Student ב-Java, ניתן ל-Requery ליצור טבלת SQLite,
נוסיף שלושה תלמידים פעם אחת ונציג אותם במסך.
flowchart LR
S["Student.java"] --> P["Requery processor"]
P --> G["StudentEntity + Models"]
G --> D["טבלת Student ב-SQLite"]
D --> V["תוצאה דרך View Binding"]
נקודת ההתחלה
צרו ב-Android Studio פרויקט Empty Views Activity:
- Name:
sqlrequery - Package name:
com.example.sqlrequery - Language: Java
- Minimum SDK: API 31
- Build configuration language: Kotlin DSL
אל תחליפו קובץ שלם כאשר ההוראה מציגה diff. שורה שמתחילה ב-+ נוספה, שורה
שמתחילה ב-- נמחקה, וכל שורה אחרת נשארת בדיוק כפי ש-Android Studio יצרה אותה.
מה נשנה — ומה נשאיר בשקט
| קובץ | השינוי המינימלי |
|---|---|
app/build.gradle.kts |
View Binding ושלוש תלויות Requery |
activity_main.xml |
הפיכת ה-TextView הקיים לאזור התוצאות |
MainActivity.java |
View Binding, פתיחת המסד והצגת תלמידים |
model/Student.java |
קובץ חדש: הגדרת ה-Entity |
Database.java |
קובץ חדש: תשתית המסד שנרחיב בפרקים הבאים |
לא נשנה את ה-Manifest, ה-themes, colors.xml, קובצי הגיבוי, האייקונים,
buildTypes, AppCompat, Material או ConstraintLayout. הם כבר עובדים ואינם
מפריעים למטרת הפרק.
1. מכינים Gradle בלי לפרק את התבנית
פתחו דרך Gradle Scripts את build.gradle.kts (Module :app).
1.1 מפעילים View Binding
מיד לאחר בלוק compileSdk, הוסיפו:
android {
namespace = "com.example.sqlrequery"
compileSdk {
version = release(37)
}
+ buildFeatures {
+ viewBinding = true
+ }
defaultConfig {
השינוי יגרום ל-Android ליצור מחלקה בשם ActivityMainBinding מן הקובץ
activity_main.xml. לא נצטרך להשתמש ב-findViewById.
1.2 מוסיפים את Requery
בתחתית dependencies, הוסיפו שלוש שורות. כל התלויות שיצרה התבנית — כולל
תלויות הבדיקות — נשארות במקומן:
dependencies {
implementation(libs.activity.ktx)
implementation(libs.appcompat)
implementation(libs.constraintlayout)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(libs.ext.junit)
+ implementation("io.requery:requery:1.6.0")
+ implementation("io.requery:requery-android:1.6.0")
+ annotationProcessor("io.requery:requery-processor:1.6.0")
}
שלוש השורות החדשות ממלאות תפקידים שונים:
| תלות | תפקיד |
|---|---|
requery |
annotations ו-API של entities ושאילתות typed |
requery-android |
חיבור Requery ל-SQLite הרגיל של Android |
requery-processor |
יצירת מחלקות Java בזמן build |
annotationProcessor הוא כלי build. הוא יקרא את Student.java וייצר קוד לפני
שהקומפילציה מסתיימת.
אין שום שינוי ב-gradle/libs.versions.toml, ב-defaultConfig, בתיקיות test
ו-androidTest, או בקובצי הבדיקה שבתוכן.
בצעו Sync Project with Gradle Files ואז Build > Make Project. התבנית עדיין אמורה להיבנות כפי שנוצרה, וכעת גם שלוש ספריות Requery זמינות.
2. עוברים ל-View Binding בשינוי מקומי
2.1 מכינים את ה-TextView הקיים
פתחו app > res > layout > activity_main.xml. אל תחליפו את ה-layout ואל תמחקו
את ה-ConstraintLayout. שנו רק את ה-TextView שכבר נמצא בו:
<TextView
+ android:id="@+id/results"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:fontFamily="monospace"
android:text="Hello World!"
+ android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
ה-id החדש ייצור ב-View Binding את השדה binding.results. הכתב המונוספייס
יעזור להציג את ה-id והשם בעמודות קריאות.
2.2 מחברים את ה-Activity ל-binding
פתחו app > kotlin+java > com.example.sqlrequery > MainActivity והוסיפו import:
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import com.example.sqlrequery.databinding.ActivityMainBinding;
בתחילת המחלקה הוסיפו שדה:
public class MainActivity extends AppCompatActivity {
+ private ActivityMainBinding binding;
@Override
כעת החליפו רק את שתי השורות שמשתמשות ב-layout וב-findViewById. שאר קוד
ה-Edge-to-Edge וה-insets נשאר:
לפני
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
- setContentView(R.layout.activity_main);
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
אחרי
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
+ binding = ActivityMainBinding.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;
});
}
binding.main מתייחס ל-id שכבר היה על ה-ConstraintLayout בתבנית.
binding.results יתייחס ל-id שהוספנו ל-TextView.
בצעו שוב Build > Make Project והריצו. Hello World! עדיין צריך להופיע. כך
אנחנו יודעים שמעבר ה-View Binding עובד עוד לפני שחיברנו את מסד הנתונים.
3. מגדירים Student Entity
תחת app > kotlin+java > com.example.sqlrequery, צרו package בשם model.
בתוכו צרו interface בשם Student:
package com.example.sqlrequery.model;
import io.requery.Entity;
import io.requery.Generated;
import io.requery.Key;
import io.requery.Persistable;
import io.requery.Table;
/** Defines one row in the SQLite {@code Student} table. */
@Entity
@Table(name = "Student")
public interface Student extends Persistable {
/** Returns the SQLite-generated primary key. */
@Key @Generated
int getId();
/** Returns the student's display name. */
String getName();
}
@Entityמבקש מ-Requery ליצור מחלקה שאפשר לשמור.@Table(name = "Student")קובע את שם הטבלה ב-SQLite.@Keyמגדיר מפתח ראשי.@Generatedמאפשר ל-SQLite להקצות id בזמן insert.Persistableמתאים את הישות ל-EntityDataStoreשניצור מיד.
בונים לפני שמשתמשים בקוד שנוצר
בצעו Build > Make Project. רק לאחר build זה Requery תיצור:
com.example.sqlrequery.model.StudentEntitycom.example.sqlrequery.model.Models
אל תכתבו ואל תערכו את הקבצים האלה. StudentEntity מוסיפה setters ו-attributes
typed כמו StudentEntity.ID; Models.DEFAULT מתאר ל-Requery את ה-schema.
4. מוסיפים את תשתית המסד
תחת app > kotlin+java > com.example.sqlrequery, צרו class בשם Database:
package com.example.sqlrequery;
import android.content.Context;
import com.example.sqlrequery.model.Models;
import com.example.sqlrequery.model.Student;
import com.example.sqlrequery.model.StudentEntity;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.sql.EntityDataStore;
/** Contains the database setup and Student operations used in the first lesson. */
final class Database {
private static EntityDataStore<Persistable> data;
/**
* Opens the normal Android SQLite database through Requery.
*
* @param context any Android context
* @return the shared store used for inserts and typed queries
*/
static synchronized EntityDataStore<Persistable> open(Context context) {
if (data == null) {
DatabaseSource source = new DatabaseSource(
context.getApplicationContext(),
Models.DEFAULT,
"class-netflix.db",
1);
source.setLoggingEnabled(true);
data = new EntityDataStore<>(source.getConfiguration());
}
return data;
}
/** Inserts the example students only when the Student table is empty. */
static void seedIfEmpty(EntityDataStore<Persistable> data) {
if (data.count(Student.class).get().value() != 0) return;
addStudent(data, "Maya");
addStudent(data, "Noa");
addStudent(data, "Dana");
}
/** Creates and inserts one Student row. */
static StudentEntity addStudent(EntityDataStore<Persistable> data, String name) {
StudentEntity student = new StudentEntity();
student.setName(name);
return data.insert(student);
}
private Database() {}
}
הקובץ חדש, ולכן כאן נכון להציג אותו בשלמותו. שלושת חלקיו ימשיכו איתנו:
openפותחת פעם אחת אתclass-netflix.dbותישאר תשתית המסד בכל הסדרה.seedIfEmptyמונעת הוספה חוזרת בכל restart; בפרק הבא נרחיב אותה.addStudentתשמש עכשיו ל-seed ובהמשך גם לכפתור הוספת תלמיד.
DatabaseSource מחבר את Requery אל SQLite של Android. הוא מקבל את
Models.DEFAULT, שם קובץ וגרסת schema. הפעלת logging מאפשרת לראות ב-Logcat את
ה-SQL שנוצר בלי לכתוב SQL ידנית.
5. פותחים, שולפים ומציגים
חזרו אל MainActivity.
5.1 imports ושדה המסד
לאחר ה-import של ActivityMainBinding, הוסיפו:
import com.example.sqlrequery.databinding.ActivityMainBinding;
+import com.example.sqlrequery.model.Student;
+import com.example.sqlrequery.model.StudentEntity;
+import io.requery.Persistable;
+import io.requery.query.Result;
+import io.requery.sql.EntityDataStore;
הוסיפו את שדה המסד לצד binding:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
+ private EntityDataStore<Persistable> data;
5.2 מחברים למסד במקום הנכון
בסוף onCreate, אבל לא בתוך ה-listener של ה-insets, הוסיפו שלוש שורות:
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;
});
+ data = Database.open(this);
+ Database.seedIfEmpty(data);
+ showStudents();
}
הסדר חשוב: פותחים את המסד, מוסיפים seed אם הוא ריק, ורק אז שולפים למסך.
5.3 מוסיפים את הפעולה שמציגה תלמידים
בתוך המחלקה, מתחת ל-onCreate, הוסיפו:
/** Selects every Student in ID order and displays the rows. */
private void showStudents() {
StringBuilder text = new StringBuilder("STUDENTS\n\n");
try (Result<Student> students = data.select(Student.class)
.orderBy(StudentEntity.ID)
.get()) {
for (Student student : students) {
text.append(student.getId())
.append(" | ")
.append(student.getName())
.append('\n');
}
}
binding.results.setText(text);
}
data.select(Student.class) היא שאילתה typed. גם המיון משתמש ב-attribute
שנוצר, StudentEntity.ID, ולא בשם עמודה כמחרוזת. Result נסגר אוטומטית
בסיום ה-try-with-resources.
6. מריצים ובודקים
הריצו את האפליקציה. במסך אמור להופיע:
STUDENTS
1 | Maya
2 | Noa
3 | Dana
בהפעלה הראשונה, Logcat אמור להציג פעולות דומות ל:
create table Student (...)
select count(*) from Student
insert into Student (name) values (?)
insert into Student (name) values (?)
insert into Student (name) values (?)
select id, name from Student order by id
סגרו והריצו שוב. עדיין צריכות להופיע בדיוק שלוש שורות. הפעם לא אמורות להיות
פעולות insert, מפני ש-seedIfEmpty מצאה טבלה שאינה ריקה.
כדי לבדוק first run מחדש, מחקו את נתוני האפליקציה או התקינו אותה מחדש. אל
תמחקו את בדיקת ה-count: בלעדיה כל פתיחה תוסיף עוד שלושה תלמידים.
רשימת בדיקה
- AppCompat, Material, Activity KTX ו-ConstraintLayout נשארו מן התבנית.
- ה-Manifest, ה-themes וקובצי המשאבים שלא נדרשו לא השתנו.
- קטלוג הספריות, קובצי test, test runner ותלויות הבדיקה נשארו ללא שינוי.
StudentEntityו-Modelsנוצרו ב-build ולא נכתבו ידנית.- המסך מציג Maya, Noa ו-Dana דרך
binding.results. - restart אינו מוסיף שורות חדשות.
הגענו לצעד ראשון runnable בשינויים ממוקדים: שמרנו את תבנית Android, הוספנו
תשתית מסד שתמשיך לפרקים הבאים, והחלפנו רק את הקוד שנדרש כדי לראות Student
אמיתי מקצה לקצה.
מבט קדימה
הבחירות בפרק הזה נועדו לצמצם עבודה חוזרת:
| מה נכתב עכשיו | מה יקרה בהמשך |
|---|---|
Database.open ושם המסד |
יישארו ללא שינוי |
addStudent |
תשמש גם את מסך ההוספה |
seedIfEmpty |
תתרחב ל-Movie ול-Watching |
Student |
תישאר צד אחד של הקשר |
binding ו-results |
יישארו בכל שלושת הפרקים |
showStudents |
תוחלף בתצוגת הקשר ואחר כך ב-INNER JOIN typed |
רכיבי התבנית שנשארו אינם חוב טכני של התלמיד. הם יכולים להישאר גם בפרקים הבאים; נשנה קובץ קיים שוב רק כאשר תכונה חדשה באמת דורשת זאת.