2 komentarze

SQLite w Androidzie – kompletny poradnik dla początkujących

Lipiec 27, 2011 Content Providers Podstawowe komponenty Przechowywanie danych Tutoriale

Przechowywanie danych jest bardzo ważnym tematem dla wszystkich twórców aplikacji (nie tylko tych mobilnych). Tak naprawdę każdy programista powinien mieć przynajmniej ogólne pojęcie o mechanizmach, które są za to odpowiedzialne. Zatem aby nie pozostawać w tyle, w dzisiejszym artykule przyjrzymy się bliżej obsłudze bazy danych, która została dostarczona domyślnie z systemem Android. Mowa tu oczywiście o SQLite, z którego w dzisiejszych czasach korzysta większość zaawansowanych urządzeń mobilnych.

O przechowywaniu danych w Androidzie

System Android aktualnie dostarcza nam kilka mechanizmów związanych z przechowywaniem danych, z których każdy ma inne zastosowanie:

  • SharedPreferences – przechowuje pary klucz-wartość, czyli niewielkie ilości danych. Mechanizm wykorzystywany jest przede wszystkim do przechowywania ustawień aplikacji.
  • Pliki – w związku z wykorzystaniem bazy SQLite, która nie obsługuje przechowywania plików, dane binarne (zdjęcia, filmy, inne pliki) powinniśmy przechowywać w ich niezmiennej formie, na karcie lub pamięci wbudowanej w urządzenie.
  • Baza danych SQLite – wykorzystywane do przechowywania dużej ilości uporządkowanych danych, które ze względu na swoja ilość wymagają wysokiej wydajności dostępu.

Jak zbudować prostą bazę w Androidzie

W dzisiejszym artykule zbudujemy kompletną klasę obsługującą prostą bazę danych, którą następnie wykorzystamy w naszej aplikacji – TodoManagerze. Nasza baza będzie składała się z jednej tabeli, przechowującej zadania, która będzie złożona z 3 pól:

  • _id – identyfikator zadania,
  • description – opis zadania,
  • completed – pole oznaczające czy nasze zadanie oznaczyliśmy jako wykonane, czy nie.

Początek

W związku z tym, że nasza baza będzie prostą konstrukcją, wystarczy nam jedna klasa, która będzie odpowiedzialna za całą jej obsługę. Tworzymy zatem klasę TodoDbAdapter:

public class TodoDbAdapter {
	private static final String DEBUG_TAG = "SqLiteTodoManager";
}

Pole DEBUG_TAG nie jest wymagane, aczkolwiek przyda nam się później do wyświetlania komunikatów w LogCacie (za pomocą metody Log.d(…)).

Tworzenie i aktualizowanie bazy danych

Pierwszą sprawą, którą się zajmiemy będzie stworzenie pól statycznych opisujących naszą bazę danych. Zaczyniemy od informacji ogólnych:

public class TodoDbAdapter {
	private static final String DEBUG_TAG = "SqLiteTodoManager";

	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "database.db";
	private static final String DB_TODO_TABLE = "todo";
}

Kolejno:

  • Pole DB_VERSION w naszym przypadku będzie oznaczało wersję bazy danych. Posłuży nam ono wtedy, gdy system będzie musiał rozpoznać czy struktura naszej bazy się zmieniła i czy powinien dokonać aktualizacji (będzie o tym kilka akapitów później).
  • DB_NAME to nazwa pliku, w którym przechowywana będzie nasza baza. Można go będzie odnaleźć w naszym urządzeniu, w katalogu: /data/data/<namespace aplikacji>/databases/.
  • DB_TODO_TABLE będzie nazwą naszej tabeli.

Następnie stworzymy kilka stałych opisujących kolumny naszej tabeli:

	...
	public static final String KEY_ID = "_id";
	public static final String ID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
	public static final int ID_COLUMN = 0;
	public static final String KEY_DESCRIPTION = "description";
	public static final String DESCRIPTION_OPTIONS = "TEXT NOT NULL";
	public static final int DESCRIPTION_COLUMN = 1;
	public static final String KEY_COMPLETED = "completed";
	public static final String COMPLETED_OPTIONS = "INTEGER DEFAULT 0";
	public static final int COMPLETED_COLUMN = 2;
}

Są to jak widać pola, o których wspominałem we wstępie. Każde z nich posiada swoją nazwę (KEY_…), typ i dodatkowe właściwości (…_OPTIONS) i numer porządkowy kolumny (…_COLUMN – bardzo przydatny w momencie pobierania danych).

Jeszcze dwie stałe do tworzenia i usuwania bazy (a w zasadzie samej tabeli, z której będzie składała się nasza baza danych):

	...
	private static final String DB_CREATE_TODO_TABLE =
			"CREATE TABLE " + DB_TODO_TABLE + "( " +
			KEY_ID + " " + ID_OPTIONS + ", " +
			KEY_DESCRIPTION + " " + DESCRIPTION_OPTIONS + ", " +
			KEY_COMPLETED + " " + COMPLETED_OPTIONS +
			");";
	private static final String DROP_TODO_TABLE =
			"DROP TABLE IF EXISTS " + DB_TODO_TABLE;
}

Dla pewności, oto cała treść pola DB_CREATE_TODO_TABLE:

CREATE TABLE todo( _id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL, completed INTEGER DEFAULT 0);

SQLiteOpenHelper – pomoc przy tworzeniu i aktualizowaniu bazy

Następnie do naszej klasy dodajemy pola niezbędne do jej funkcjonowania:

	...
	private SQLiteDatabase db;
	private Context context;
	private DatabaseHelper dbHelper;

oraz definicję klasy DatabaseHelper:

	...
	private static class DatabaseHelper extends SQLiteOpenHelper {
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(DB_CREATE_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database creating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " ver." + DB_VERSION + " created");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL(DROP_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database updating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " updated from ver." + oldVersion + " to ver." + newVersion);
			Log.d(DEBUG_TAG, "All data is lost.");

			onCreate(db);
		}
	}

Klasa DatabaseHelper, która dziedziczy po SQLiteOpenHelper jest mechanizmem tworzącym i/lub aktualizującym strukturę naszej bazy. Posiada ona dwie nadpisane metody:

  • onCreate(SQLiteDatabase db) – metoda wywoływana w momencie, gdy odwołujemy się do bazy danych, która jeszcze fizycznie nigdzie nie istnieje. W naszym przypadku wykonujemy kod SQL, który stworzy naszą tabelę.
  • onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) – metoda ściśle związana z wersją naszej bazy, zapisaną w stałej DB_VERSION. Wywoływana jest w momencie gdy w urządzeniu istnieje starsza wersja bazy, i służy do aktualizacji jej struktury, do wersji najnowszej.
    Uwaga – w naszym przypadku idziemy na łatwiznę, i każda nowa wersja bazy powoduje usunięcie starej tabeli, a następnie utworzenie nowej. Zaleca się jednak by w metodzie onUpgrade(…) przeprowadzać przepisanie danych do nowej wersji bazy, zamiast ich usuwanie.

Otwieranie i zamykanie bazy danych

Stworzymy teraz jeszcze 3 metody (w zasadzie konstruktor i 2 metody), aby zamknąć temat tworzenia bazy danych. Do naszej klasy TodoDbAdapter dodajemy:

Prosty konstruktor:

	...
	public TodoDbAdapter(Context context) {
		this.context = context;
	}

Metodę, która otworzy połączenie z bazą danych:

	...
	public TodoDbAdapter open(){
		dbHelper = new DatabaseHelper(context, DB_NAME, null, DB_VERSION);
		try {
			db = dbHelper.getWritableDatabase();
		} catch (SQLException e) {
			db = dbHelper.getReadableDatabase();
		}
		return this;
	}

Przy czym należy zwrócić uwagę na dwie rzeczy:

  • To w tym miejscu przekazujemy wersję naszej bazy danych (argument konstruktora naszej klasy DatabaseHelper), na podstawie której system zdecyduje czy przeprowadzać aktualizację czy nie.
  • Blok try/catch zastosowany został na wypadek gdyby w system nie mógł zwrócić pełnego dostępu (odczyt/zapis) do naszej bazy. Może się to zdarzyć np. gdy w naszym urządzeniu brakuje pamięci lub działa ona w trybie „tylko do odczytu”. W takim wypadku możemy jeszcze poratować się dostępem read only do naszej bazy.

I jeszcze metoda zamykająca połączenie z bazą:

	...
	public void close() {
		dbHelper.close();
	}

Gdyby ktoś w tym miejscu miał wątpliwości – oto cały kod, jaki dotychczas stworzyliśmy:

public class TodoDbAdapter {
	private static final String DEBUG_TAG = "SqLiteTodoManager";

	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "database.db";
	private static final String DB_TODO_TABLE = "todo";

	public static final String KEY_ID = "_id";
	public static final String ID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
	public static final int ID_COLUMN = 0;
	public static final String KEY_DESCRIPTION = "description";
	public static final String DESCRIPTION_OPTIONS = "TEXT NOT NULL";
	public static final int DESCRIPTION_COLUMN = 1;
	public static final String KEY_COMPLETED = "completed";
	public static final String COMPLETED_OPTIONS = "INTEGER DEFAULT 0";
	public static final int COMPLETED_COLUMN = 2;

	private static final String DB_CREATE_TODO_TABLE =
			"CREATE TABLE " + DB_TODO_TABLE + "( " +
			KEY_ID + " " + ID_OPTIONS + ", " +
			KEY_DESCRIPTION + " " + DESCRIPTION_OPTIONS + ", " +
			KEY_COMPLETED + " " + COMPLETED_OPTIONS +
			");";
	private static final String DROP_TODO_TABLE =
			"DROP TABLE IF EXISTS " + DB_TODO_TABLE;

	private SQLiteDatabase db;
	private Context context;
	private DatabaseHelper dbHelper;

	private static class DatabaseHelper extends SQLiteOpenHelper {
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(DB_CREATE_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database creating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " ver." + DB_VERSION + " created");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL(DROP_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database updating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " updated from ver." + oldVersion + " to ver." + newVersion);
			Log.d(DEBUG_TAG, "All data is lost.");

			onCreate(db);
		}
	}

	public TodoDbAdapter(Context context) {
		this.context = context;
	}

	public TodoDbAdapter open(){
		dbHelper = new DatabaseHelper(context, DB_NAME, null, DB_VERSION);
		try {
			db = dbHelper.getWritableDatabase();
		} catch (SQLException e) {
			db = dbHelper.getReadableDatabase();
		}
		return this;
	}

	public void close() {
		dbHelper.close();
	}
}

Jak to działa w tym momencie

Gdybyśmy teraz wykorzystali nasz adapter bazy danych, efektem jego działania byłoby:

  • Podczas pierwszego uruchomienia – utworzenie pliku /data/data/<pakiet aplikacji>/databases/database.dboraz informacja z LogCat:
    DEBUG/SqLiteTodoManager(286): Database creating...
    DEBUG/SqLiteTodoManager(286): Table todo ver.1 created
  • Podczas kolejnego uruchomienia nic by się nie zmieniło,
  • Podczas uruchomienia, w którym wartość DB_VERSION zmienilibyśmy na wyższą (np. z 1 na 2):
    DEBUG/SqLiteTodoManager(518): Database updating...
    DEBUG/SqLiteTodoManager(518): Table todo updated from ver.1 to ver.2
    DEBUG/SqLiteTodoManager(518): All data is lost.
    DEBUG/SqLiteTodoManager(518): Database creating...
    DEBUG/SqLiteTodoManager(518): Table todo ver.2 created

Dodawanie, usuwanie i aktualizowanie danych

Stworzymy teraz kilka metod, które pozwolą nam na manipulację danymi umieszczonymi w naszej bazie. Nim jednak się tym zajmiemy, dodamy klasę służącą za model naszych danych – TodoTask:

package pl.froger.sqlitetodomanager.model;

public class TodoTask {
	private long id;
	private String description;
	private boolean completed;

	public TodoTask(long id, String description, boolean completed) {
		this.id = id;
		this.description = description;
		this.completed = completed;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isCompleted() {
		return completed;
	}

	public void setCompleted(boolean completed) {
		this.completed = completed;
	}
}

Dodawanie danych

Pierwsza metoda będzie odpowiedzialna za stworzenie nowego wpisu w naszej bazie (przypomina, że jest to metoda klasy TodoDbAdater):

	...
	public long insertTodo(String description) {
		ContentValues newTodoValues = new ContentValues();
		newTodoValues.put(KEY_DESCRIPTION, description);
		return db.insert(DB_TODO_TABLE, null, newTodoValues);
	}

Metoda tworzy nowy wpis jedynie na podstawie ciągu znakowego description, bowiem id jest generowane automatycznie, natomiast pole completed ma wartość domyślną 0.

Do zapisywania danych (w zasadzie do przekazywania ich do zapytania) służy klasa ContentValues. Za pomocą metod put(…) umieszczamy w niej pary klucz-wartość (gdzie klucz jest nazwą kolumny w naszej tabeli).
Następnie cały zbiór takich wartości (w postaci obiektu ContentValues) przekazujemy do metody insert(…). Zwraca ona id ostatnio zapisanego wiersza, lub -1 w wypadku błędu (dokumentacja).

Aktualizacja danych

Dodamy teraz dwie metody (w zasadzie jedną) odpowiedzialne za aktualizację wiersza danych:

	...
	public boolean updateTodo(TodoTask task) {
		long id = task.getId();
		String description = task.getDescription();
		boolean completed = task.isCompleted();
		return updateTodo(id, description, completed);
	}

	public boolean updateTodo(long id, String description, boolean completed) {
		String where = KEY_ID + "=" + id;
		int completedTask = completed ? 1 : 0;
		ContentValues updateTodoValues = new ContentValues();
		updateTodoValues.put(KEY_DESCRIPTION, description);
		updateTodoValues.put(KEY_COMPLETED, completedTask);
		return db.update(DB_TODO_TABLE, updateTodoValues, where, null) > 0;
	}

Jak widać, podobnie jak przy dodawaniu danych, również i tutaj wykorzystujemy obiekt ContentValues do przekazania danych. Dla ciekawych – linijka 102 sluży do zamiany typu boolean na wartość liczbową (SQLite nie obsługuje typu wartości logicznych).
Metoda update(…) mówi chyba sama za siebie, w związku z czym nie będziemy się w nią zagłębiać. Zwraca ona ilość zaktualizowanych pól. (dokumentacja)

Usuwanie danych

W naszym przypadku chyba najprostsza metoda, w której nie ma co tłumaczyć (zwraca ilość usuniętych pól):

	...
	public boolean deleteTodo(long id){
		String where = KEY_ID + "=" + id;
		return db.delete(DB_TODO_TABLE, where, null) > 0;
	}

(dokumentacja)

Zwracanie danych

Z naszego punktu widzenia jest to najciekawszy fragment kodu. Oto dwie metody które go realizują:

	...
	public Cursor getAllTodos() {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		return db.query(DB_TODO_TABLE, columns, null, null, null, null, null);
	}

	public TodoTask getTodo(long id) {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		String where = KEY_ID + "=" + id;
		Cursor cursor = db.query(DB_TODO_TABLE, columns, where, null, null, null, null);
		TodoTask task = null;
		if(cursor != null && cursor.moveToFirst()) {
			String description = cursor.getString(DESCRIPTION_COLUMN);
			boolean completed = cursor.getInt(COMPLETED_COLUMN) > 0 ? true : false;
			task = new TodoTask(id, description, completed);
		}
		return task;
	}

Jak widać, w obu z nich wykorzystywany jest obiekt klasy Cursor. W Androidzie służy on do reprezentacji danych zwracanych przez różnego rodzaju zapytania (niekoniecznie muszą one być skierowane do bazy danych). Nawigacja po danych (które w obiekcie Cursor zapisane są wierszami) odbywa się za pomocą metod:

  • moveToFirst() – przesuwa kursor na pierwszy wiersz zwróconego wyniku,
  • moveToNext() – przesuwa kursor na następny wiersz wyniku,
  • moveToPrevious() – j.w. – na poprzedni,
  • moveToPosition() – przesuwa kursor na zadaną pozycję,
  • getPosition() – zwraca aktualną pozycję obiektu Cursor.

Aby pobrać dane z konkretnego wiersza możemy skorzystać z jednej z metod get…(int columnIndex), które zwracają dane znajdujące się pod kolumną, której numer przekazujemy jako argument. I stąd też pola statyczne …_COLUMN, które zdefiniowaliśmy na początku naszej klasy.
Polecam zapoznać się z dokumentacją klasy Cursor, która zawiera szereg innych, ciekawych metod, z których możemy skorzystać.

Nie będę też tłumaczył metody query(…), z której argumentami polecam zapoznać się w dokumentacji.

Koniec

I tym sposobem zakończyliśmy budowę klasy do obsługi naszej bazy danych. :) Oto kompletny kod źródłowy TodoDbAdapter.java:

package pl.froger.sqlitetodomanager.database;

import pl.froger.sqlitetodomanager.model.TodoTask;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class TodoDbAdapter {
	private static final String DEBUG_TAG = "SqLiteTodoManager";

	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "database.db";
	private static final String DB_TODO_TABLE = "todo";

	public static final String KEY_ID = "_id";
	public static final String ID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
	public static final int ID_COLUMN = 0;
	public static final String KEY_DESCRIPTION = "description";
	public static final String DESCRIPTION_OPTIONS = "TEXT NOT NULL";
	public static final int DESCRIPTION_COLUMN = 1;
	public static final String KEY_COMPLETED = "completed";
	public static final String COMPLETED_OPTIONS = "INTEGER DEFAULT 0";
	public static final int COMPLETED_COLUMN = 2;

	private static final String DB_CREATE_TODO_TABLE =
			"CREATE TABLE " + DB_TODO_TABLE + "( " +
			KEY_ID + " " + ID_OPTIONS + ", " +
			KEY_DESCRIPTION + " " + DESCRIPTION_OPTIONS + ", " +
			KEY_COMPLETED + " " + COMPLETED_OPTIONS +
			");";
	private static final String DROP_TODO_TABLE =
			"DROP TABLE IF EXISTS " + DB_TODO_TABLE;

	private SQLiteDatabase db;
	private Context context;
	private DatabaseHelper dbHelper;

	private static class DatabaseHelper extends SQLiteOpenHelper {
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(DB_CREATE_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database creating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " ver." + DB_VERSION + " created");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL(DROP_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database updating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " updated from ver." + oldVersion + " to ver." + newVersion);
			Log.d(DEBUG_TAG, "All data is lost.");

			onCreate(db);
		}
	}

	public TodoDbAdapter(Context context) {
		this.context = context;
	}

	public TodoDbAdapter open(){
		dbHelper = new DatabaseHelper(context, DB_NAME, null, DB_VERSION);
		try {
			db = dbHelper.getWritableDatabase();
		} catch (SQLException e) {
			db = dbHelper.getReadableDatabase();
		}
		return this;
	}

	public void close() {
		dbHelper.close();
	}

	public long insertTodo(String description) {
		ContentValues newTodoValues = new ContentValues();
		newTodoValues.put(KEY_DESCRIPTION, description);
		return db.insert(DB_TODO_TABLE, null, newTodoValues);
	}

	public boolean updateTodo(TodoTask task) {
		long id = task.getId();
		String description = task.getDescription();
		boolean completed = task.isCompleted();
		return updateTodo(id, description, completed);
	}

	public boolean updateTodo(long id, String description, boolean completed) {
		String where = KEY_ID + "=" + id;
		int completedTask = completed ? 1 : 0;
		ContentValues updateTodoValues = new ContentValues();
		updateTodoValues.put(KEY_DESCRIPTION, description);
		updateTodoValues.put(KEY_COMPLETED, completedTask);
		return db.update(DB_TODO_TABLE, updateTodoValues, where, null) > 0;
	}

	public boolean deleteTodo(long id){
		String where = KEY_ID + "=" + id;
		return db.delete(DB_TODO_TABLE, where, null) > 0;
	}

	public Cursor getAllTodos() {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		return db.query(DB_TODO_TABLE, columns, null, null, null, null, null);
	}

	public TodoTask getTodo(long id) {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		String where = KEY_ID + "=" + id;
		Cursor cursor = db.query(DB_TODO_TABLE, columns, where, null, null, null, null);
		TodoTask task = null;
		if(cursor != null && cursor.moveToFirst()) {
			String description = cursor.getString(DESCRIPTION_COLUMN);
			boolean completed = cursor.getInt(COMPLETED_COLUMN) > 0 ? true : false;
			task = new TodoTask(id, description, completed);
		}
		return task;
	}
}

Przykładowa aplikacja

Aby wykorzystać nasza adapter bazy danych w akcji, zbudujemy teraz bardzo prosty TodoManager, który z niej skorzysta. Do zrozumienia większości jego kodu wystarczą nam podstawowe informacje o Aktywnościach oraz elemencie ListView (i innych prostych elementach UI).
Zaznaczam, że poniższy opis będzie raczej mało szczegółowy. Szczególną uwagę zwrócimy jedynie na elementy powiązane z dzisiejszym wpisem.

Przygotowanie

Oto dwa zrzuty ekranu, które prezentują naszą aplikację:

Stan normalny:

Stan po kliknięciu przycisku New task:

W obu przypadkach pole wypełniające ekran nad przyciskami to ListView, w którym będzie przechowywana lista naszych zadań. Do zarządzania widocznością elementów wykorzystany został atrybut android:visibility i wartości visible lub gone (który nie tylko ukrywa element, ale całkowicie usuwa go z ekranu).

Kod źródłowy plików main.xml, todo_list_item.xml (wygląd pojedynczego wiersza listy), oraz strings.xml można znaleźć na dole tego wpisu.

Adapter listy

Oto kompletny kod źródłowy adaptera elementu ListView. Jest on niemal w całości oparty na adapterze, którego opis zawarłem we wpisie ListView, pierwsze kroki, w związku z czym uwagę poświęcę jedynie kilku zaznaczonym linijkom kodu.

TodoTasksAdapter.java

package pl.froger.sqlitetodomanager;

import java.util.List;

import pl.froger.sqlitetodomanager.model.TodoTask;
import android.app.Activity;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TodoTasksAdapter extends ArrayAdapter<TodoTask> {
	private Activity context;
	private List<TodoTask> tasks;
	public TodoTasksAdapter(Activity context, List<TodoTask> tasks) {
		super(context, R.layout.todo_list_item, tasks);
		this.context = context;
		this.tasks = tasks;
	}

	static class ViewHolder {
		public TextView tvTodoDescription;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder;
		View rowView = convertView;
		if(rowView == null) {
			LayoutInflater layoutInflater = context.getLayoutInflater();
			rowView = layoutInflater.inflate(R.layout.todo_list_item, null, true);
			viewHolder = new ViewHolder();
			viewHolder.tvTodoDescription = (TextView) rowView.findViewById(R.id.tvTodoDescription);
			rowView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) rowView.getTag();
		}
		TodoTask task = tasks.get(position);
		viewHolder.tvTodoDescription.setText(task.getDescription());
		if(task.isCompleted()) {
			viewHolder.tvTodoDescription
				.setPaintFlags(viewHolder.tvTodoDescription.getPaintFlags() |
						Paint.STRIKE_THRU_TEXT_FLAG);
		} else {
			viewHolder.tvTodoDescription
				.setPaintFlags(viewHolder.tvTodoDescription.getPaintFlags() &
						~Paint.STRIKE_THRU_TEXT_FLAG);
		}
		return rowView;
	}
}

Zaznaczone linijki odpowiedzialne są za przekreślanie (lub usuwanie przekreślenia) wykonanych zadań (link do snippetu).

Kod źródłowy Aktywności

Oto co mamy na początku (podświetlone zostały dwie metody, które zaimplementujemy):

public class MainActivity extends Activity {
	private Button btnAddNew;
	private Button btnClearCompleted;
	private Button btnSave;
	private Button btnCancel;
	private EditText etNewTask;
	private ListView lvTodos;
	private LinearLayout llControlButtons;
	private LinearLayout llNewTaskButtons;

	private TodoDbAdapter todoDbAdapter;
	private Cursor todoCursor;
	private List<TodoTask> tasks;
	private TodoTasksAdapter listAdapter;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiElements();
		initListView();
		initButtonsOnClickListeners();
	}

	private void initUiElements() {
		btnAddNew = (Button) findViewById(R.id.btnAddNew);
		btnClearCompleted = (Button) findViewById(R.id.btnClearCompleted);
		btnSave = (Button) findViewById(R.id.btnSave);
		btnCancel = (Button) findViewById(R.id.btnCancel);
		etNewTask = (EditText) findViewById(R.id.etNewTask);
		lvTodos = (ListView) findViewById(R.id.lvTodos);
		llControlButtons = (LinearLayout) findViewById(R.id.llControlButtons);
		llNewTaskButtons = (LinearLayout) findViewById(R.id.llNewTaskButtons);
	}
}

W powyższym kodzie nie ma nic, czemu należałoby poświęcić więcej uwagi. Przechodzimy zatem do inicjalizacji naszej listy zadań.

Lista zadań

Najpierw dodajemy metodę ogólną, slużącą do inicjalizacji naszej listy:

	private void initListView() {
		fillListViewData();
		initListViewOnItemClick();
	}

Na początku zajmiemy się pierwszą metodą, która wypełni naszą listę danymi. Oto komplet metod, które będą za to odpowiedzialne:

	private void fillListViewData() {
		todoDbAdapter = new TodoDbAdapter(getApplicationContext());
		todoDbAdapter.open();
		getAllTasks();
		listAdapter = new TodoTasksAdapter(this, tasks);
		lvTodos.setAdapter(listAdapter);
	}

	private void getAllTasks() {
		tasks = new ArrayList<TodoTask>();
		todoCursor = getAllEntriesFromDb();
		updateTaskList();
	}

	private Cursor getAllEntriesFromDb() {
		todoCursor = todoDbAdapter.getAllTodos();
		if(todoCursor != null) {
			startManagingCursor(todoCursor);
			todoCursor.moveToFirst();
		}
		return todoCursor;
	}

	private void updateTaskList() {
		if(todoCursor != null && todoCursor.moveToFirst()) {
			do {
				long id = todoCursor.getLong(TodoDbAdapter.ID_COLUMN);
				String description = todoCursor.getString(TodoDbAdapter.DESCRIPTION_COLUMN);
				boolean completed = todoCursor.getInt(TodoDbAdapter.COMPLETED_COLUMN) > 0 ? true : false;
				tasks.add(new TodoTask(id, description, completed));
			} while(todoCursor.moveToNext());
		}
	}

	@Override
	protected void onDestroy() {
		if(todoDbAdapter != null)
			todoDbAdapter.close();
		super.onDestroy();
	}

Idąc po kolei:

  • linijka 64 oraz linijki 98,99 - w tych miejscach otwierane i zamykane jest połączenie z bazą danych. Należy pamiętać by takowe połączenie zamknąć (zwykle robi się to tak jak u nas, w metodzie onDestroy()). System oczywiście sam potrafi zamknąć połączenie, jednak jest to sygnalizowane ostrzeżeniem w Logach.
  • linijka 72 -> 76 – w tym miejscu pobierany jest obiekt Cursor naszych danych. Dobrym zwyczajem jest ustawianie go na pierwszym elemencie za pomocą metody moveToFirst().
    Metoda startManagingCursor(Cursor c) należąca do klasy Aktywności jest bardzo przydatnym mechanizmem, bowiem integruje czas życia kursora z czasem życia Aktywności w której się znajduje. Dzięki temu kiedy Aktywność zostanie wstrzymana, automatycznie wywołana zostaje metoda deactivate(), a gdy wznowiona - requery(). Pierwsza z nich sprawia, że wszelkie wywołania obiektu cursor kończą się niepowodzeniem, druga natomiast wyłącza ten stan (pozwala na wywołania) oraz odświeża dane, na które wskazuje obiekt Cursor. Przy okazji warto również wspomnieć o metodzie stopManagingCursor(Cursor c), która oddziela czas życia obiektu Cursor od czasu życia Aktywności.
  • linijka 85 – metoda jest kolejnym (drugim w tym artykule) przykładem na użycia obiektu Cursor. Wypełnia ona listę zadań przechowywaną w jednym z pól naszej Aktywności.

Pozostała nam jeszcze implementacja zachowania po kliknięciu na element listy. Chcielibyśmy by aplikacja oznaczała nasze zadanie jako wykonane (lub odznaczała po ponownym kliknięciu). Oto jak powinien wyglądać taki kod:

	private void initListViewOnItemClick() {
		lvTodos.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View v, int position,
					long id) {
				TodoTask task = tasks.get(position);
				if(task.isCompleted()){
					todoDbAdapter.updateTodo(task.getId(), task.getDescription(), false);
				} else {
					todoDbAdapter.updateTodo(task.getId(), task.getDescription(), true);
				}
				updateListViewData();
			}
		});
	}

	private void updateListViewData() {
		todoCursor.requery();
		tasks.clear();
		updateTaskList();
		listAdapter.notifyDataSetChanged();
	}
  • linijki 110, 112 – wykorzystujemy tutaj zaimplementowane przez nas metody aktualizujące wpis w bazie.
  • linijka 114->119 – metoda, która aktualizuje naszą listę. Najpierw poprzez requery() aktualizujemy dane zawarte w obiekcie Cursor, następnie czyścimy listę zadań, którą ponownie wypełniamy poprzez zaimplementowaną chwilę wcześniej metodę. Na końcu informujemy adapter naszego ListView o aktualizacji danych.

W tym momencie metody inicjalizujące naszą listę są skończone. Pozostała już tylko…

Obsługa przycisków

Zaczniemy od kodu nie wymagającego wyjaśnienia:

	private void initButtonsOnClickListeners() {
		OnClickListener onClickListener = new OnClickListener() {
			@Override
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnAddNew:
					addNewTask();
					break;
				case R.id.btnSave:
					saveNewTask();
					break;
				case R.id.btnCancel:
					cancelNewTask();
					break;
				case R.id.btnClearCompleted:
					clearCompletedTasks();
					break;
				default:
					break;
				}
			}
		};
		btnAddNew.setOnClickListener(onClickListener);
		btnClearCompleted.setOnClickListener(onClickListener);
		btnSave.setOnClickListener(onClickListener);
		btnCancel.setOnClickListener(onClickListener);
	}

	private void showOnlyNewTaskPanel() {
		setVisibilityOf(llControlButtons, false);
		setVisibilityOf(llNewTaskButtons, true);
		setVisibilityOf(etNewTask, true);
	}

	private void showOnlyControlPanel() {
		setVisibilityOf(llControlButtons, true);
		setVisibilityOf(llNewTaskButtons, false);
		setVisibilityOf(etNewTask, false);
	}

	private void setVisibilityOf(View v, boolean visible) {
		int visibility = visible ? View.VISIBLE : View.GONE;
		v.setVisibility(visibility);
	}

	private void hideKeyboard() {
		InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
		imm.hideSoftInputFromWindow(etNewTask.getWindowToken(), 0);
	}

Pomiędzy linijką 154 a 174 znajduje się kod odpowiedzialny za ukrywanie/pokazywanie elementów naszego UI (oraz klawiatury), z którego skorzystamy podczas implementacji zachowań naszych przycisków.

Oto implementacja podświetlonych metod:

	private void addNewTask(){
		showOnlyNewTaskPanel();
	}

	private void saveNewTask(){
		String taskDescription = etNewTask.getText().toString();
		if(taskDescription.equals("")){
			etNewTask.setError("Your task description couldn't be empty string.");
		} else {
			todoDbAdapter.insertTodo(taskDescription);
			etNewTask.setText("");
			hideKeyboard();
			showOnlyControlPanel();
		}
		updateListViewData();
	}

	private void cancelNewTask() {
		etNewTask.setText("");
		showOnlyControlPanel();
	}

	private void clearCompletedTasks(){
		if(todoCursor != null && todoCursor.moveToFirst()) {
			do {
				if(todoCursor.getInt(TodoDbAdapter.COMPLETED_COLUMN) == 1) {
					long id = todoCursor.getLong(TodoDbAdapter.ID_COLUMN);
					todoDbAdapter.deleteTodo(id);
				}
			} while (todoCursor.moveToNext());
		}
		updateListViewData();
	}
  • linijka 185 – wykorzystujemy tu kolejną metodę naszego adaptera bazy danych, dodającą nowy wpis do tabeli zadań.
  • linijka 190 – jeżeli dodany nowy wpis do bazy, nie zapomnijmy o odświeżeniu danych naszej listy.
  • linijka 198 – ostatnie już wykorzystanie obiektu Cursor. Metoda przechodzi po wszystkich zadaniach i usuwa z niej te, które są już wykonane.

Koniec

I tym sposobem dobrnęliśmy do samego końca naszego przykładu.

Screeny

Kompletny kod źródłowy

Cały projekt dostępny jest na naszym Githubie – SQLiteTodoManager.

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<ListView
		android:layout_width="fill_parent"
		android:id="@+id/lvTodos"
		android:layout_weight="1"
		android:layout_height="wrap_content"></ListView>
	<EditText
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:id="@+id/etNewTask"
		android:hint="@string/edittext_hint_new_task"
		android:visibility="gone">
		<requestFocus></requestFocus>
	</EditText>
	<LinearLayout
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:id="@+id/llNewTaskButtons"
		android:visibility="gone">
		<Button
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:id="@+id/btnSave"
			android:text="@string/button_save"></Button>
		<Button
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:id="@+id/btnCancel"
			android:text="@string/button_cancel"></Button>
	</LinearLayout>
	<LinearLayout
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:id="@+id/llControlButtons">
		<Button
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:id="@+id/btnAddNew"
			android:text="@string/button_add"
			android:layout_width="fill_parent"></Button>
		<Button
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:id="@+id/btnClearCompleted"
			android:text="@string/button_clear"
			android:layout_width="fill_parent"></Button>
	</LinearLayout>
</LinearLayout>

todo_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:orientation="horizontal"
	android:padding="15dp">
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/tvTodoDescription"
		android:layout_marginLeft="20dp"
		android:text="test"
		android:textAppearance="?android:attr/textAppearanceLarge"></TextView>
</LinearLayout>

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="app_name">SqLiteTodoManager</string>
	<string name="button_add">New task</string>
	<string name="button_clear">Clear completed</string>
	<string name="edittext_hint_new_task">Type here your new task...</string>
	<string name="button_save">Save task</string>
	<string name="button_cancel">Cancel</string>
</resources>

MainActivity.java

package pl.froger.sqlitetodomanager;

import java.util.ArrayList;
import java.util.List;

import pl.froger.sqlitetodomanager.database.TodoDbAdapter;
import pl.froger.sqlitetodomanager.model.TodoTask;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;

public class MainActivity extends Activity {
	private Button btnAddNew;
	private Button btnClearCompleted;
	private Button btnSave;
	private Button btnCancel;
	private EditText etNewTask;
	private ListView lvTodos;
	private LinearLayout llControlButtons;
	private LinearLayout llNewTaskButtons;

	private TodoDbAdapter todoDbAdapter;
	private Cursor todoCursor;
	private List<TodoTask> tasks;
	private TodoTasksAdapter listAdapter;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initUiElements();
		initListView();
		initButtonsOnClickListeners();
	}

	private void initUiElements() {
		btnAddNew = (Button) findViewById(R.id.btnAddNew);
		btnClearCompleted = (Button) findViewById(R.id.btnClearCompleted);
		btnSave = (Button) findViewById(R.id.btnSave);
		btnCancel = (Button) findViewById(R.id.btnCancel);
		etNewTask = (EditText) findViewById(R.id.etNewTask);
		lvTodos = (ListView) findViewById(R.id.lvTodos);
		llControlButtons = (LinearLayout) findViewById(R.id.llControlButtons);
		llNewTaskButtons = (LinearLayout) findViewById(R.id.llNewTaskButtons);
	}

	private void initListView() {
		fillListViewData();
		initListViewOnItemClick();
	}

	private void fillListViewData() {
		todoDbAdapter = new TodoDbAdapter(getApplicationContext());
		todoDbAdapter.open();
		getAllTasks();
		listAdapter = new TodoTasksAdapter(this, tasks);
		lvTodos.setAdapter(listAdapter);
	}

	private void getAllTasks() {
		tasks = new ArrayList<TodoTask>();
		todoCursor = getAllEntriesFromDb();
		updateTaskList();
	}

	private Cursor getAllEntriesFromDb() {
		todoCursor = todoDbAdapter.getAllTodos();
		if(todoCursor != null) {
			startManagingCursor(todoCursor);
			todoCursor.moveToFirst();
		}
		return todoCursor;
	}

	private void updateTaskList() {
		if(todoCursor != null && todoCursor.moveToFirst()) {
			do {
				long id = todoCursor.getLong(TodoDbAdapter.ID_COLUMN);
				String description = todoCursor.getString(TodoDbAdapter.DESCRIPTION_COLUMN);
				boolean completed = todoCursor.getInt(TodoDbAdapter.COMPLETED_COLUMN) > 0 ? true : false;
				tasks.add(new TodoTask(id, description, completed));
			} while(todoCursor.moveToNext());
		}
	}

	@Override
	protected void onDestroy() {
		if(todoDbAdapter != null)
			todoDbAdapter.close();
		super.onDestroy();
	}

	private void initListViewOnItemClick() {
		lvTodos.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View v, int position,
					long id) {
				TodoTask task = tasks.get(position);
				if(task.isCompleted()){
					todoDbAdapter.updateTodo(task.getId(), task.getDescription(), false);
				} else {
					todoDbAdapter.updateTodo(task.getId(), task.getDescription(), true);
				}
				updateListViewData();
			}
		});
	}

	private void updateListViewData() {
		todoCursor.requery();
		tasks.clear();
		updateTaskList();
		listAdapter.notifyDataSetChanged();
	}

	private void initButtonsOnClickListeners() {
		OnClickListener onClickListener = new OnClickListener() {
			@Override
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnAddNew:
					addNewTask();
					break;
				case R.id.btnSave:
					saveNewTask();
					break;
				case R.id.btnCancel:
					cancelNewTask();
					break;
				case R.id.btnClearCompleted:
					clearCompletedTasks();
					break;
				default:
					break;
				}
			}
		};
		btnAddNew.setOnClickListener(onClickListener);
		btnClearCompleted.setOnClickListener(onClickListener);
		btnSave.setOnClickListener(onClickListener);
		btnCancel.setOnClickListener(onClickListener);
	}

	private void showOnlyNewTaskPanel() {
		setVisibilityOf(llControlButtons, false);
		setVisibilityOf(llNewTaskButtons, true);
		setVisibilityOf(etNewTask, true);
	}

	private void showOnlyControlPanel() {
		setVisibilityOf(llControlButtons, true);
		setVisibilityOf(llNewTaskButtons, false);
		setVisibilityOf(etNewTask, false);
	}

	private void setVisibilityOf(View v, boolean visible) {
		int visibility = visible ? View.VISIBLE : View.GONE;
		v.setVisibility(visibility);
	}

	private void hideKeyboard() {
		InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
		imm.hideSoftInputFromWindow(etNewTask.getWindowToken(), 0);
	}

	private void addNewTask(){
		showOnlyNewTaskPanel();
	}

	private void saveNewTask(){
		String taskDescription = etNewTask.getText().toString();
		if(taskDescription.equals("")){
			etNewTask.setError("Your task description couldn't be empty string.");
		} else {
			todoDbAdapter.insertTodo(taskDescription);
			etNewTask.setText("");
			hideKeyboard();
			showOnlyControlPanel();
		}
		updateListViewData();
	}

	private void cancelNewTask() {
		etNewTask.setText("");
		showOnlyControlPanel();
	}

	private void clearCompletedTasks(){
		if(todoCursor != null && todoCursor.moveToFirst()) {
			do {
				if(todoCursor.getInt(TodoDbAdapter.COMPLETED_COLUMN) == 1) {
					long id = todoCursor.getLong(TodoDbAdapter.ID_COLUMN);
					todoDbAdapter.deleteTodo(id);
				}
			} while (todoCursor.moveToNext());
		}
		updateListViewData();
	}
}

TodoTasksAdapter.java

package pl.froger.sqlitetodomanager;

import java.util.List;

import pl.froger.sqlitetodomanager.model.TodoTask;
import android.app.Activity;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TodoTasksAdapter extends ArrayAdapter<TodoTask> {
	private Activity context;
	private List<TodoTask> tasks;
	public TodoTasksAdapter(Activity context, List<TodoTask> tasks) {
		super(context, R.layout.todo_list_item, tasks);
		this.context = context;
		this.tasks = tasks;
	}

	static class ViewHolder {
		public TextView tvTodoDescription;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder;
		View rowView = convertView;
		if(rowView == null) {
			LayoutInflater layoutInflater = context.getLayoutInflater();
			rowView = layoutInflater.inflate(R.layout.todo_list_item, null, true);
			viewHolder = new ViewHolder();
			viewHolder.tvTodoDescription = (TextView) rowView.findViewById(R.id.tvTodoDescription);
			rowView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) rowView.getTag();
		}
		TodoTask task = tasks.get(position);
		viewHolder.tvTodoDescription.setText(task.getDescription());
		if(task.isCompleted()) {
			viewHolder.tvTodoDescription
				.setPaintFlags(viewHolder.tvTodoDescription.getPaintFlags() |
						Paint.STRIKE_THRU_TEXT_FLAG);
		} else {
			viewHolder.tvTodoDescription
				.setPaintFlags(viewHolder.tvTodoDescription.getPaintFlags() &
						~Paint.STRIKE_THRU_TEXT_FLAG);
		}
		return rowView;
	}
}

TodoDbAdapter.java

package pl.froger.sqlitetodomanager.database;

import pl.froger.sqlitetodomanager.model.TodoTask;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class TodoDbAdapter {
	private static final String DEBUG_TAG = "SqLiteTodoManager";

	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "database.db";
	private static final String DB_TODO_TABLE = "todo";

	public static final String KEY_ID = "_id";
	public static final String ID_OPTIONS = "INTEGER PRIMARY KEY AUTOINCREMENT";
	public static final int ID_COLUMN = 0;
	public static final String KEY_DESCRIPTION = "description";
	public static final String DESCRIPTION_OPTIONS = "TEXT NOT NULL";
	public static final int DESCRIPTION_COLUMN = 1;
	public static final String KEY_COMPLETED = "completed";
	public static final String COMPLETED_OPTIONS = "INTEGER DEFAULT 0";
	public static final int COMPLETED_COLUMN = 2;

	private static final String DB_CREATE_TODO_TABLE =
			"CREATE TABLE " + DB_TODO_TABLE + "( " +
			KEY_ID + " " + ID_OPTIONS + ", " +
			KEY_DESCRIPTION + " " + DESCRIPTION_OPTIONS + ", " +
			KEY_COMPLETED + " " + COMPLETED_OPTIONS +
			");";
	private static final String DROP_TODO_TABLE =
			"DROP TABLE IF EXISTS " + DB_TODO_TABLE;

	private SQLiteDatabase db;
	private Context context;
	private DatabaseHelper dbHelper;

	private static class DatabaseHelper extends SQLiteOpenHelper {
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(DB_CREATE_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database creating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " ver." + DB_VERSION + " created");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL(DROP_TODO_TABLE);

			Log.d(DEBUG_TAG, "Database updating...");
			Log.d(DEBUG_TAG, "Table " + DB_TODO_TABLE + " updated from ver." + oldVersion + " to ver." + newVersion);
			Log.d(DEBUG_TAG, "All data is lost.");

			onCreate(db);
		}
	}

	public TodoDbAdapter(Context context) {
		this.context = context;
	}

	public TodoDbAdapter open(){
		dbHelper = new DatabaseHelper(context, DB_NAME, null, DB_VERSION);
		try {
			db = dbHelper.getWritableDatabase();
		} catch (SQLException e) {
			db = dbHelper.getReadableDatabase();
		}
		return this;
	}

	public void close() {
		dbHelper.close();
	}

	public long insertTodo(String description) {
		ContentValues newTodoValues = new ContentValues();
		newTodoValues.put(KEY_DESCRIPTION, description);
		return db.insert(DB_TODO_TABLE, null, newTodoValues);
	}

	public boolean updateTodo(TodoTask task) {
		long id = task.getId();
		String description = task.getDescription();
		boolean completed = task.isCompleted();
		return updateTodo(id, description, completed);
	}

	public boolean updateTodo(long id, String description, boolean completed) {
		String where = KEY_ID + "=" + id;
		int completedTask = completed ? 1 : 0;
		ContentValues updateTodoValues = new ContentValues();
		updateTodoValues.put(KEY_DESCRIPTION, description);
		updateTodoValues.put(KEY_COMPLETED, completedTask);
		return db.update(DB_TODO_TABLE, updateTodoValues, where, null) > 0;
	}

	public boolean deleteTodo(long id){
		String where = KEY_ID + "=" + id;
		return db.delete(DB_TODO_TABLE, where, null) > 0;
	}

	public Cursor getAllTodos() {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		return db.query(DB_TODO_TABLE, columns, null, null, null, null, null);
	}

	public TodoTask getTodo(long id) {
		String[] columns = {KEY_ID, KEY_DESCRIPTION, KEY_COMPLETED};
		String where = KEY_ID + "=" + id;
		Cursor cursor = db.query(DB_TODO_TABLE, columns, where, null, null, null, null);
		TodoTask task = null;
		if(cursor != null && cursor.moveToFirst()) {
			String description = cursor.getString(DESCRIPTION_COLUMN);
			boolean completed = cursor.getInt(COMPLETED_COLUMN) > 0 ? true : false;
			task = new TodoTask(id, description, completed);
		}
		return task;
	}
}

TodoTask.java

package pl.froger.sqlitetodomanager.model;

public class TodoTask {
	private long id;
	private String description;
	private boolean completed;

	public TodoTask(long id, String description, boolean completed) {
		this.id = id;
		this.description = description;
		this.completed = completed;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isCompleted() {
		return completed;
	}

	public void setCompleted(boolean completed) {
		this.completed = completed;
	}
}

Komentarze (2) Subskrybuj

 

  1. [...] (Notifications) są jednym z podstawowych komponentów systemu (obok Aktywności, Intencji oraz Dostarczycieli treści). Jest to zbiór elementów, które „zaczepiają” użytkownika za pomocą specjalnie do [...]

  2. MST pisze:

    Mając w każdym wierszu listy button „deleteRow”, jak zrobić by akurat ten wiersz usunąć?
    Jak do tego podejść, w getView otworzyć bazę, tylko jak odebrać KEY_ID by wykorzystać metodę z adaptera bazy?

    //Mam taką metodę w adapterze bazy danych
    public boolean deleteObiekty(long id) {
    String where = KEY_ID + „=” + id;
    return db.delete(DB_TABLE, where , null) > 0;
    }

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.