Сложно представить серьезное приложение, которое не нуждается в хранении данных. В Android существует несколько способов для этого: SharedPreferences, использование файлов, использование веб-сервисов и Sqlite базы данных. Сегодня мы рассмотрим последний вариант в достаточном объеме.

Суть проблемы

При создании приложения с базой данных можно разделить работу на 3 этапа:

  • Описание моделей и проектирование базы данных на их основе
  • Описание классов для управления данными в базе
  • Привязка данных к представлению (пользовательскому интерфейсу)

Мы пройдем все три чтобы создать наше приложение, которое назовем IdevBabysName.

Шаг 1: описание моделей. Или что мы вообще будем делать?

В качестве примера создадим приложение BabysName, которое поможет выбрать имя ребенку из списка вариантов.

Функционал: добавить имя, удалить имя, выбрать случайное имя из списка.

Скриншот все расскажет:


Обрадуйте малыша, напишите хорошо!

Тут нам достаточно одной модели  — имени. Опишем ее к классе Name:


package by.idev.android.babyssql;
public class Name {
private long id;
private String name;
public Name(String name) {
this.name = name;
}
public Name(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}

В ней достаточно двух полей:собственно имя в виде строки  и id в виде long. Исходя из этого наша база будет иметь два поля: целочисленное для id, и текстовое name.

Внимание! Всегда задавайте для поля id имя «_id» в вашей базе! Это соглашение, и если вы не будите следовать ему ваша база не сможет быть использована со многими стандартными классами и Content Provider‘ми.

Шаг 2: SQLiteOpenHelper — создание, открытие и управление версиями БД

На самом деле существует два метода открытия базы: напрямую методом openOrCreateDatabase() контекста либо использовать класс SQLiteOpenHelper  для более тонкой работы.

Рассмотрим первый вариант, код будет иметь следующий вид:


private static final String DB_NAME = "somebase.sqlite3";
private static final String TABLE_NAME = "sometable";
//Подготовленный запрос для создания таблицы в базе
private static final String DB_CREATE_QUERY =
"CREATE TABLE " + TABLE_NAME +
"(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"column_one TEXT NOT NULL);";
SQLiteDataBase someDb;
private void openDataBase() {
//Создаем базу в памяти устройства
someDb = openOrCreateDataBase(DB_NAME, Context.MODE_PRIVATE, null);
//Используем sql-запрос для создания таблицы
someDb.execSQL(DB_CREATE_QUERY);
}

Этот код можно поместить в ваш Activity или Application, и работать с базой someDb. Это быстрый метод подходящий для простых «одноразовых» приложений, в более серьезном проекте желательно использовать второй подход, вы очень быстро поймете почему.

Приведем описание нашего openhelper‘а:


// Класс-помошник отвечающий за создание/отктрытие
// базы и осуществляющий контроль ее версий
private static class DbOpenHelper extends SQLiteOpenHelper {
public DbOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
// Вызывается при создании базы на устройстве
public void onCreate(SQLiteDatabase db) {
// Посроим стандартный sql-запрос для создания таблицы
final String CREATE_DB = "CREATE TABLE " + TABLE_NAME + " ("
+ KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ KEY_NAME + " TEXT NOT NULL);";
db.execSQL(CREATE_DB);
}
@Override
// Метод будет вызван, если изменится версия базы
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Тут можно организовать миграцию данных из старой базы в новую
// или просто "выбросить" таблицу и создать заново
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}

Перегрузка двух указанных методов обязательна, как и описание конструктора. Смысл методов примерно таков:

  1. onCreate() — запускается если база еще не создана, то есть обычно при первом запуске приложения. В нем опишем код чегобывыдумали? Да, создания базы.
  2. onUpgrade() — будет вызван, если изменилась версия базы данных. Допустим, если в нее добавили новый столбец, тогда код в этом методе будет иметь вид:
db.execSQL(ALTER TABLE table_name ADD column_name datatype);

В нашем случае мы просто заменим таблицу на новую, чтобы не вносить сложности в пример.

Вы наверняка заметили, что DbOpenHelper обявлен как private static. Все потому, что он будет статическим вложенным классом у класса SqlAdapter, который свяжет контролер (Activity) и базу данных. Сама Activity не будет знать о базе данных, а лишь обращаться к методам адаптера. Таким образом мы сможем менять наш источник данных не меняя код контролера. И это хорошо!

Шаг 2: описания адаптера и операций с базой данных

Наш адаптер будет унаследован от класса BaseAdapter, что позволит нам использовать его с ListView. Для начала представлю его код полностью, а потом рассмотрим интересные нам методы в индивидуальном порядке:


package by.idev.android.babyssql;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class SqlAdapter extends BaseAdapter {
private static final String DB_NAME = "babys_names.sqlite3";
private static final String TABLE_NAME = "names";
private static final int DB_VESION = 1;
// Для удобства выполнения sql-запросов
// создадим константы с именами полей таблицы
// и номерами соответсвующих столбцов
private static final String KEY_ID = "_id";
private static final int ID_COLUMN = 0;
private static final String KEY_NAME = "name";
private static final int NAME_COLUMN = 1;
private Cursor cursor;
private SQLiteDatabase database;
private DbOpenHelper dbOpenHelper;
private Context context;
//Далее следуют обязательные к перегрузке методы адаптера
public SqlAdapter(Context context) {
super();
this.context = context;
init();
}
@Override
public long getItemId(int position) {
Name nameOnPosition = getItem(position);
return nameOnPosition.getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parrent) {
TextView textView;
if (null == convertView) {
textView = (TextView) View.inflate(context, R.layout.list_item,
null);
} else {
textView = (TextView) convertView;
}
textView.setText(getItem(position).getName());
return textView;
}
@Override
public int getCount() {
return cursor.getCount();
}
@Override
public Name getItem(int position) {
if (cursor.moveToPosition(position)) {
long id = cursor.getLong(ID_COLUMN);
String name = cursor.getString(NAME_COLUMN);
Name nameOnPositon = new Name(id, name);
return nameOnPositon;
} else {
throw new CursorIndexOutOfBoundsException(
"Cant move cursor to postion");
}
}
//Методы для работы с базой данных
public Cursor getAllEntries() {
//Список колонок базы, которые следует включить в результат
String[] columnsToTake = { KEY_ID, KEY_NAME };
// составляем запрос к базе
return database.query(TABLE_NAME, columnsToTake,
null, null, null, null, KEY_ID);
}
public long addItem(Name name) {
ContentValues values = new ContentValues();
values.put(KEY_NAME, name.getName());
long id = database.insert(TABLE_NAME, null, values);
refresh();
return id;
}
public boolean removeItem(Name nameToRemove) {
boolean isDeleted = (database.delete(TABLE_NAME, KEY_NAME + "=?",
new String[] { nameToRemove.getName() })) > 0;
refresh();
return isDeleted;
}
public boolean updateItem(long id, String newName) {
ContentValues values = new ContentValues();
values.put(KEY_NAME, newName);
boolean isUpdated = (database.update(TABLE_NAME, values, KEY_ID + "=?",
new String[] {id+""})) > 0;
return isUpdated;
}
//Прочие служебные методывфзеук
public void onDestroy() {
dbOpenHelper.close();
}
//Вызывает обновление вида
private void refresh() {
cursor = getAllEntries();
notifyDataSetChanged();
}
// Инициализация адаптера: открываем базу и создаем курсор
private void init() {
dbOpenHelper = new DbOpenHelper(context, DB_NAME, null, DB_VESION);
try {
database = dbOpenHelper.getWritableDatabase();
} catch (SQLException e) {
// /Если база не открылась, то дальше нам дороги нет
// но это особый случай
Log.e(this.toString(), "Error while getting database");
throw new Error("The end");
}
cursor = getAllEntries();
}
// Класс-помошник отвечающий за создание/отктрытие
// базы и осуществляющий контроль ее версий
private static class DbOpenHelper extends SQLiteOpenHelper {
//код был представлени ранее
}
}

«И о чем эта песня?» С константами все должно быть понятно, они нужны для составления запросов. Первая группа методов позволяет нашему адаптеру общаться с ListView, мы не будем останавливаться на этом, так как это не тема статьи. Если вы никогда не писали своего кастомного  адаптера, то просто верьте на слово =)

Нас гораздо больше интересует группа методов для работы с базой данных. И в самом первом из них мы встречаемся с классом Cursor. Объект этого класса возвращается нам после запроса в базу данных. Он очень напоминает итератор своими методами moveToFirst(), moveToNext() etc. Получить курсор можно следующим образом:

public Cursor getAllEntries() {

//Список колонок базы, которые следует включить в результат
String[] columnsToTake = { KEY_ID, KEY_NAME };
// составляем запрос к базе
return database.query(TABLE_NAME, columnsToTake,
null, null, null, null, KEY_ID);
}

Метод query() класса SQLiteDatabase в простейшем случае принимает имя таблицы, массив строк с именами столбцов которые нужно поместить в результат  и последнем значением имя столбца, по которому результат будет отсортирован.

Один из перегруженных методов адаптера является по совместительству и методом использующим курсор, то есть тоже «наш клиент»:

@Override

public Name getItem(int position) {
if (cursor.moveToPosition(position)) {
long id = cursor.getLong(ID_COLUMN);
String name = cursor.getString(NAME_COLUMN);
Name nameOnPositon = new Name(id, name);
return nameOnPositon;
} else {
throw new CursorIndexOutOfBoundsException(
"Cant move cursor to postion");
}
}

Тут мы видим пример извлечения данных из курсора. Замечу, что данные в sqlite слабо типизированные, т.е. вы можете прочитать дробное число в строку и все в этом духе. Если бы вам нужно было извлечь все записи, то это можно было бы сделать так:

		ArrayList names = new ArrayList();

cursor.moveToFirst();
if (!cursor.isAfterLast()) {
do {
long id = cursor.getLong(ID_COLUMN);
String name = cursor.getString(NAME_COLUMN);
names.add(new Name(id, name));
} while (cursor.moveToNext());
}

После этого массив names будет содержать все имена из базы данных.

Рассмотрим добавление элемента в базу:

	public long addItem(Name name) {

ContentValues values = new ContentValues();
values.put(KEY_NAME, name.getName());
long id = database.insert(TABLE_NAME, null, values);
refresh();
return id;
}

Тут мы сталкиваемся к классом ContentValue, объекты которого содержат множество пар типа «ключ-значение», где ключ — имя колонки в БД, а значение… это ее значение! Метод insert() достаточно прост, вы уже сами заметили. Но что это за метод refresh()? Он из области служебных методов:

	//Вызывает обновление вида

private void refresh() {
cursor = getAllEntries();
notifyDataSetChanged();
}

Его роль в том, чтобы обеспечить нас «свежим» курсором с последними данными и оповестить вид об изменениях в данных. Мы будем обращаться к этому методу каждый раз после внесения изменений в базу.

Методы removeItem()  и updateItem() очень похожи, рассмотрим первый:

	public boolean removeItem(Name nameToRemove) {

boolean isDeleted = (database.delete(TABLE_NAME, KEY_NAME + "=?",
new String[] { nameToRemove.getName() })) > 0;
refresh();
return isDeleted;
}

Второй аргумент метода delete() это ничто иное как WHERE из SQL, третий же — это его аргументы, на которые будут заменены знаки «?» (whildcard) в описании WHERE. Таким образом наше выражение примет вид:

DELETE FROM TABLE table_name WHERE key_name = nameToRemove

продолжение тут, а исходники тут IdevBabysSQL

Вакантное место: ведущий Android разработчик в компании Softeq Development

Что будете делать Вы?

  • разрабатывать приложения для мобильных Android устройств
  • работать с заказчиками мирового уровня

Что нужно от Вас?

  • опыт разработки под Android от одного года
  • отличное знание основ Java
  • отличное знание основ Android
  • хорошее знание С/С++ и опыт работы с NDK
  • знание принципов ООП и способность эффективно применять их в архитектуре приложения
  • знание и умение применять на практике шаблоны проектирования
  • английский язык: уметь уверенно читать и писать, разговорный приветствуется

Большим плюсом будет:

  • опыт кросс-платформенного программирования

Сайт: www.softeq.by, www.softeq.com

Cтраничка в Facebook: http://www.facebook.com/pages/Softeq/110374298801

Страничка в VK: http://vk.com/club21079655

Контактная информация:

Ждем Ваше резюме на jobs@softeq.by!

Если вы хотите узнать о вакансии и нашей компании больше, Вы можете связаться с менеджером по персоналу Ириной Протащик:

Skype: irina.protaschik

E-mail: irina.protaschik@softeq.com

Моб.: +375 29 175 47 00, +375 29 234 47 00

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *