ברוך הבא לדוגמת השימוש ב-Android SQLite. Android SQLite הוא הדרך המועדפת ביותר לאחסון נתונים באפליקציות המתבצעות במערכת אנדרואיד. עבור רבות מתוך האפליקציות, SQLite הוא הישג הבסיס, בין אם משתמשים בו ישירות או דרך חבילת צד שלישי. למטה יש המופיעה האפליקציה הסופית שניצור היום באמצעות מסד נתונים של Android SQLite.
Android SQLite
Android SQLite הוא מסד נתונים קל מאוד שמגיע עם מערכת ההפעלה Android. Android SQLite משלבת ממשק SQL נקי עם השפעת זיכרון קטנה מאוד ומהירות סבירה. עבור Android, SQLite "מוכנת" בזמן ריצה של Android, כך שכל אפליקציה יכולה ליצור את מסדי הנתונים שלה. API המקומי של Android SQLite אינו JDBC, מאחר ש-JDBC עשוי להיות יתר על יכולתם של טלפון חכם המוגבל בזיכרון. לאחר יצירת מסד נתונים בהצלחה, הוא ממוקם ב-data/data//databases/ ונגיש מתוך Android Device Monitor. SQLite הוא מסד נתונים רלציוני אשר מכיל טבלאות (המורכבות משורות ועמודות), אינדקסים ועוד. אנו יכולים ליצור טבלאות משלנו כדי לאחסן את הנתונים בהתאם. מבנה זה נקרא סכימה.
Android SQLite SQLiteOpenHelper
Android כוללת יכולות לטיפול בשינויים בסכימת מסד נתונים, התלויים בעיקר על שימוש במחלקת SQLiteOpenHelper
. SQLiteOpenHelper מיועדת לפתרון שני בעיות נפוצות מאוד.
- כאשר היישום רץ לראשונה – בנקודה זו, אין לנו עדיין מסד נתונים. נצטרך ליצור את הטבלאות, האינדקסים, הנתונים המתחילים, וכו'
- כאשר היישום משודרג לסכימה חדשה – המסד הנתונים שלנו עדיין יהיה על סכימה הישנה מהמהדורה הישנה יותר של היישום. נהיה לנו אפשרות לשנות את סכימת המסד כך שתתאים לצרכי שאר היישום.
SQLiteOpenHelper
מעטף את הלוגיקה הזו כדי ליצור ולשדרג מסד נתונים על פי ההגדרות שלנו. לכן נצטרך ליצור מחלקת מחלקה מותאמת אישית של SQLiteOpenHelper
וליישם לפחות את השלושה שיטות הבאות.
-
בנאי : מקבל את ההקשר (לדוגמה, פעילות), שם המסד נתונים, יצרן קורסור אופציונלי (נדון בכך מאוחר יותר), ומספר שלם המייצג את גרסת סכימת מסד הנתונים שאתה משתמש בה (רגיל להתחיל מ-1 ולהעלות מאוחר יותר).
public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); }
-
onCreate(SQLiteDatabase db) : נקרא כאשר אין מסד נתונים והיישום זקוק לאחד. הוא מעביר לנו אובייקט
SQLiteDatabase
, המצביע על מסד נתונים שנוצר חדש, שאנו יכולים למלא עם טבלאות ונתונים ראשוניים. -
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) : נקרא כאשר גרסת סכימת המסד הנתונים שאנחנו צריכים אינה תואמת את גרסת סכימת המסד הנתונים הקיימת, הוא מעביר לנו אובייקט SQLiteDatabase ואת מספרי הגרסה הישנה והחדשה. לכן אנו יכולים למצוא את הדרך הטובה ביותר להמיר את מסד הנתונים מסכימה הישנה לסכימה החדשה.
אנו מגדירים מחלקת DBManager
לבצע את כל פעולות CRUD של מסד הנתונים (צור, קרא, עדכן ומחק).
פתיחה וסגירה של חיבור מסד נתונים SQLite ב-Android
לפני ביצוע כל פעולות מסד נתונים כמו הוספה, עדכון, מחיקת רשומות בטבלה, יש לפתוח חיבור למסד הנתונים ראשית על ידי קריאה לשיטת getWritableDatabase() כפי שמוצג למטה:
public DBManager open() throws SQLException {
dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
ה-dbHelper הוא מופע של המחלקה המורשית של SQLiteOpenHelper
. על מנת לסגור את חיבור בסיס הנתונים נקראת השיטה הבאה.
public void close() {
dbHelper.close();
}
הכנסת רשומה חדשה לטבלת מסד נתונים SQLite ב-Android
הקטע קוד הבא מראה איך להכניס רשומה חדשה למסד נתונים של SQLite ב-Android.
public void insert(String name, String desc) {
ContentValues contentValue = new ContentValues();
contentValue.put(DatabaseHelper.SUBJECT, name);
contentValue.put(DatabaseHelper.DESC, desc);
database.insert(DatabaseHelper.TABLE_NAME, null, contentValue);
}
Content Values יוצרת ערכים ריקים בגודל התחלתי נתון. נדון בערכים האחרים במהלך הקידוד.
עדכון רשומה בטבלת מסד נתונים SQLite ב-Android
הקטע הבא מציג איך לעדכן רשומה יחידה.
public int update(long _id, String name, String desc) {
ContentValues contentValues = new ContentValues();
contentValues.put(DatabaseHelper.SUBJECT, name);
contentValues.put(DatabaseHelper.DESC, desc);
int i = database.update(DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper._ID + " = " + _id, null);
return i;
}
Android SQLite – מחיקת רשומה
רק נצטרך להעביר את המזהה של הרשומה שיש למחוק כפי שמוצג למטה.
public void delete(long _id) {
database.delete(DatabaseHelper.TABLE_NAME, DatabaseHelper._ID + "=" + _id, null);
}
Android SQLite Cursor
A Cursor represents the entire result set of the query. Once the query is fetched a call to cursor.moveToFirst() is made. Calling moveToFirst() does two things:
- זה מאפשר לנו לבדוק האם השאילתה החזירה סט ריק (על ידי בדיקת ערך ההחזרה)
- זה מעביר את הסמן לתוצאה הראשונה (כאשר הסט אינו ריק)
הקוד הבא משמש לקבלת כל הרשומות:
public Cursor fetch() {
String[] columns = new String[] { DatabaseHelper._ID, DatabaseHelper.SUBJECT, DatabaseHelper.DESC };
Cursor cursor = database.query(DatabaseHelper.TABLE_NAME, columns, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
return cursor;
}
דרך נוספת להשתמש ב־Cursor היא לארוז אותו ב־CursorAdapter
. בדיוק כמו ש־ArrayAdapter
מתאים מערכים, CursorAdapter
מתאים אובייקטי Cursor, מפנה את המידע שלהם ל־AdapterView
כמו ListView
. בואו נעבור לפרויקט שלנו שמשתמש ב־SQLite כדי לאחסן מידע משמעותי מסוים.
מבנה פרויקט לדוגמת Android SQLite
ביישום זה אנו רוצים ליצור רשומות שמאחסנות את שמות המדינות והמטבעות התואמים שלהן בצורת ListView. אנו מכסים את כל התכונות שנדונו לעיל.
קוד פרויקט Android SQLite
היישום מורכב מ-5 מחלקות. אנו מתחילים עם ההגדרה של DatabaseHelper, אשר הוא תת־מחלקה של SQLiteOpenHelper כך: DatabaseHelper.java
package com.journaldev.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
// שם הטבלה
public static final String TABLE_NAME = "COUNTRIES";
// עמודות הטבלה
public static final String _ID = "_id";
public static final String SUBJECT = "subject";
public static final String DESC = "description";
// מידע על מסד הנתונים
static final String DB_NAME = "JOURNALDEV_COUNTRIES.DB";
// גרסת מסד הנתונים
static final int DB_VERSION = 1;
// שאילתת יצירת הטבלה
private static final String CREATE_TABLE = "create table " + TABLE_NAME + "(" + _ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + SUBJECT + " TEXT NOT NULL, " + DESC + " TEXT);";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
כפי שנדונו לעיל, עברנו על השינוי של השיטות onCreate()
ו־onUpgrade()
בנוסף לקונסטרוקטור. קיבלנו שמות למסד הנתונים ולטבלה בהתאמה, JOURNALDEV_COUNTRIES.DB ו־COUNTRIES. עמודת האינדקס מתווספת באופן אוטומטי בכל פעם ששורה חדשה מתווספת. שמות העמודות עבור המדינה והמטבע הם "subject" ו־"description" בהתאמה. מחלקת DBManager היא המקום בו מאתחלים את DatabaseHelper ומגדירים את פעולות ה CRUD. להלן הקוד עבור מחלקה זו: DBManager.java
package com.journaldev.sqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class DBManager {
private DatabaseHelper dbHelper;
private Context context;
private SQLiteDatabase database;
public DBManager(Context c) {
context = c;
}
public DBManager open() throws SQLException {
dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
public void insert(String name, String desc) {
ContentValues contentValue = new ContentValues();
contentValue.put(DatabaseHelper.SUBJECT, name);
contentValue.put(DatabaseHelper.DESC, desc);
database.insert(DatabaseHelper.TABLE_NAME, null, contentValue);
}
public Cursor fetch() {
String[] columns = new String[] { DatabaseHelper._ID, DatabaseHelper.SUBJECT, DatabaseHelper.DESC };
Cursor cursor = database.query(DatabaseHelper.TABLE_NAME, columns, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
return cursor;
}
public int update(long _id, String name, String desc) {
ContentValues contentValues = new ContentValues();
contentValues.put(DatabaseHelper.SUBJECT, name);
contentValues.put(DatabaseHelper.DESC, desc);
int i = database.update(DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper._ID + " = " + _id, null);
return i;
}
public void delete(long _id) {
database.delete(DatabaseHelper.TABLE_NAME, DatabaseHelper._ID + "=" + _id, null);
}
}
המחלקה CountryListActivity.java
היא הפעילות שמתבצעת כאשר היישום מתחיל. למטה מוגדר העיצוב שלה: fragment_emp_list.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:padding="10dp" >
</ListView>
<TextView
android:id="@+id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/empty_list_text" />
</RelativeLayout>
כאן מוגדר רכיב ListView כדי לכלול את הרשומות שמאוחסנות במסד הנתונים. בתחילה, ה-ListView יהיה ריק לכן נעשה שימוש ב-TextView כדי להציג את אותו דבר. CountryListActivity.java
package com.journaldev.sqlite;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
public class CountryListActivity extends ActionBarActivity {
private DBManager dbManager;
private ListView listView;
private SimpleCursorAdapter adapter;
final String[] from = new String[] { DatabaseHelper._ID,
DatabaseHelper.SUBJECT, DatabaseHelper.DESC };
final int[] to = new int[] { R.id.id, R.id.title, R.id.desc };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_emp_list);
dbManager = new DBManager(this);
dbManager.open();
Cursor cursor = dbManager.fetch();
listView = (ListView) findViewById(R.id.list_view);
listView.setEmptyView(findViewById(R.id.empty));
adapter = new SimpleCursorAdapter(this, R.layout.activity_view_record, cursor, from, to, 0);
adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
// מאזין ללחיצה על פריטי הרשימה
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView > parent, View view, int position, long viewId) {
TextView idTextView = (TextView) view.findViewById(R.id.id);
TextView titleTextView = (TextView) view.findViewById(R.id.title);
TextView descTextView = (TextView) view.findViewById(R.id.desc);
String id = idTextView.getText().toString();
String title = titleTextView.getText().toString();
String desc = descTextView.getText().toString();
Intent modify_intent = new Intent(getApplicationContext(), ModifyCountryActivity.class);
modify_intent.putExtra("title", title);
modify_intent.putExtra("desc", desc);
modify_intent.putExtra("id", id);
startActivity(modify_intent);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.add_record) {
Intent add_mem = new Intent(this, AddCountryActivity.class);
startActivity(add_mem);
}
return super.onOptionsItemSelected(item);
}
}
בפעילות זו, נקרא לאובייקט DBManager כדי לבצע את פעולות ה-CRUD. SimpleCursorAdapter מוגדר כדי להוסיף אלמנטים לרשימה מתוצאות השאילתה שמוחזרות באובייקט Cursor. בלחיצה על פריט ברשימה מבוצעת כוונה לפתיחת מחלקת ModifyCountryActivity. התפריט מכיל פריט להוספת רשומה חדשה מתוך ActionBar. גם כאן מבוצעת כוונה לפתיחת מחלקת AddCountryActivity. למטה הקוד של menu.xml
. menu.xml
<menu xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
tools:context="com.example.sqlitesample.MainActivity" >
<item
android:id="@+id/add_record"
android:icon="@android:drawable/ic_menu_add"
android:orderInCategory="100"
android:title="@string/add_record"
app:showAsAction="always"/>
</menu>
עיצוב ה-XML והקוד של קובץ AddCountryActivity.java
מוגדרים למטה: activity_add_record.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp" >
<EditText
android:id="@+id/subject_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_title" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/description_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_desc"
android:inputType="textMultiLine"
android:minLines="5" >
</EditText>
<Button
android:id="@+id/add_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/add_record" />
</LinearLayout>
שני רכיבי EditText שמקבלים את הקלטים עבור מדינה ומטבע יחד עם כפתור להוספת הערכים למסד הנתונים ולהצגתו ב-ListView מוגדרים כאן. AddCountryActivity.java
package com.journaldev.sqlite;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class AddCountryActivity extends Activity implements OnClickListener {
private Button addTodoBtn;
private EditText subjectEditText;
private EditText descEditText;
private DBManager dbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Add Record");
setContentView(R.layout.activity_add_record);
subjectEditText = (EditText) findViewById(R.id.subject_edittext);
descEditText = (EditText) findViewById(R.id.description_edittext);
addTodoBtn = (Button) findViewById(R.id.add_record);
dbManager = new DBManager(this);
dbManager.open();
addTodoBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.add_record:
final String name = subjectEditText.getText().toString();
final String desc = descEditText.getText().toString();
dbManager.insert(name, desc);
Intent main = new Intent(AddCountryActivity.this, CountryListActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(main);
break;
}
}
}
הפעולה CRUD שנעשית כאן היא הוספת רשומה חדשה למסד הנתונים. עיצוב ה-XML והקוד של קובץ ModifyCountryActivity.java מוגדרים למטה: activity_modify_record.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp" >
<EditText
android:id="@+id/subject_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:ems="10"
android:hint="@string/enter_title" />
<EditText
android:id="@+id/description_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/enter_desc"
android:inputType="textMultiLine"
android:minLines="5" >
</EditText>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_update" />
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_delete" />
</LinearLayout>
</LinearLayout>
זהה לעיצוב הקודם רק שנוספו לו כפתורי שינוי ומחיקה. ModifyCountryActivity.java
package com.journaldev.sqlite;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class ModifyCountryActivity extends Activity implements OnClickListener {
private EditText titleText;
private Button updateBtn, deleteBtn;
private EditText descText;
private long _id;
private DBManager dbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Modify Record");
setContentView(R.layout.activity_modify_record);
dbManager = new DBManager(this);
dbManager.open();
titleText = (EditText) findViewById(R.id.subject_edittext);
descText = (EditText) findViewById(R.id.description_edittext);
updateBtn = (Button) findViewById(R.id.btn_update);
deleteBtn = (Button) findViewById(R.id.btn_delete);
Intent intent = getIntent();
String id = intent.getStringExtra("id");
String name = intent.getStringExtra("title");
String desc = intent.getStringExtra("desc");
_id = Long.parseLong(id);
titleText.setText(name);
descText.setText(desc);
updateBtn.setOnClickListener(this);
deleteBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_update:
String title = titleText.getText().toString();
String desc = descText.getText().toString();
dbManager.update(_id, title, desc);
this.returnHome();
break;
case R.id.btn_delete:
dbManager.delete(_id);
this.returnHome();
break;
}
}
public void returnHome() {
Intent home_intent = new Intent(getApplicationContext(), CountryListActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(home_intent);
}
}
הפעולות CRUD שבוצעו כאן הן עדכון ומחיקת רשומה. התמונות למטה הן צילומי מסך של הפלט הסופי של פרויקטנו. התמונה הראשונה היא הפלט שנראה כאשר האפליקציה מופעלת לראשונה. התמונה השנייה היא תוצאת לחיצה על אפשרות התפריט מתוך תפריט הפעולות כדי להוסיף רשומה חדשה כפי שמוצג למטה.
התמונה השלישית מציגה פלט כאשר מתווספות 3 רשומות:
התמונה הרביעית מציגה את הפלט כאשר לוחצים על פריט ברשימה כלשהי כדי לשנות או למחוק רשומה:
התמונה האחרונה היא הפלט כאשר רשומה נמחקת. בדוגמה זו אנו מוחקים את הרשומה הראשונה:
פתיחת קובץ מסד נתונים של Android SQLite
כפי שדנו קודם במדריך זה, קובץ מסד הנתונים מאוחסן באחסון הפנימי שנגיש מתוך נתב התקשורת של המכשיר האנדרואיד, כפי שניתן לראות בתמונה למטה. כדי לצפות במסד נתונים זה, עלינו למשוך את הקובץ מהמכשיר לשולחן העבודה שלנו. נעשה זאת על ידי לחיצה על אפשרות התפריט בפינה הימנית העליונה כפי שמוצג בתמונה למעלה:
כדי לפתוח את הקובץ, יש להוריד את SQLiteBrowser מהקישור הבא: this. המקטעים למטה מראים את הסכימה והטבלאות בדפדפן.
כדי לצפות בטבלה, יש לעבור ללשונית "גלישת נתונים" בחלק העליון. בתמונה הבאה ניתן לראות:
וכך מסתיימת המדריך שלנו ל-Android SQLite. הפרויקט הסופי של Android SQLite ניתן להורדה מהקישור למטה:
Source:
https://www.digitalocean.com/community/tutorials/android-sqlite-database-example-tutorial