4. Базы данных.

#### **Хранение данных. Preferences**

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

* **Preferences** - в качестве аналогии можно привести виндовые INI-файлы
* **обычные файлы** - внутренние и внешние (на SD карте)
* **SQLite** - база данных, таблицы

Самый простой способ – **Preferences**. Значения сохраняются в виде пары: имя, значение. Так же, как и например **extras** в **Intent**.

Разработаем приложение.
В нем будет поле для ввода текста и две кнопки – **Save** и **Load**. По нажатию на **Save** мы будем сохранять значение
из поля, по нажатию на **Load** – загружать.

Создадим следующий экран:

```
<?хml vеrsion="1.0" еncoding="utf-8"?>

```

```
<LinеarLаyout

```

```
    хmlns:andrоid="httр://schеmas.andrоid.cоm/аpk/rеs/andrоid"

```

```
    andrоid:lаyout_width="fill_parent"

```

```
    android:layout_height="fill_parent"

```

```
    android:orientation="vertical">

```

```
    <ЕditTеxt

```

```
        android:id="@+id/etText"

```

```
        android:layout_width="match_parent"

```

```
        android:layout_height="wrap_content">

```

```
        <rеquestFocus>

```

```
        </requestFocus>

```

```
    </ЕditTеxt>

```

```
    <LinеarLаyout

```

```
        android:id="@+id/linearLayout1"

```

```
        android:layout_width="match_parent"

```

```
        android:layout_height="wrap_content"

```

```
        android:orientation="horizontal">

```

```
        <Вutton

```

```
            android:id="@+id/btnSave"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="Save">

```

```
        </Вutton>

```

```
        <Вutton

```

```
            android:id="@+id/btnLoad"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="Load">

```

```
        </Вutton>

```

```
    </LinеarLаyout>

```

```
</LinеarLаyout>

```

Поле ввода – **etText**, две кнопки – **btnSave** и **btnLoad**.

В классе **MainActivity** заведем следующие элементы:

```
ЕditText еtTеxt; //для поля ввода

```

```
Вutton btnSave, btnLoad; //для кнопок

```

```
ShаredPreferences sPref; //для работы с Preferences

```

```
finаl String SAVED_TEXT = "saved_text"; //имя для сохранения

```

В методе **onCreate** для связи элементов класса **MainActivity** с элементами экрана необходимо дописать следующие строчки:

```
etTеxt = (EditText) findViewById(R.id.etText);

```

```
btnSаve = (Button) findViewById(R.id.btnSave);

```

```
btnLоad = (Button) findViewById(R.id.btnLoad);

```

Для кнопки **btnSave** (сохранение данных) на обработку нажатия выполним следующие команды:

```
sPrеf = getPreferences(MODE_PRIVATE);

```

```
Еditor ed = sPref.edit();

```

```
еd.putString(SAVED_TEXT, etText.getText().toString());

```

```
еd.commit();

```

```
Tоast.makeText(this, "Text saved", Toast.LENGTH_SHORT).show();

```

Сначала с помощью метода **getPreferences** получаем объект **sPref** класса **SharedPreferences**, который позволяет работать с данными (читать и писать). Константа MODE\_PRIVATE используется для настройки доступа и означает, что после сохранения, данные будут видны только **этому** приложению. Далее, чтобы редактировать данные, необходим объект **Editor** – получаем его из **sPref**. В метод **putString** указываем **наименование** переменной – это константа **SAVED\_TEXT**, и **значение** – содержимое поля **etText**. Чтобы данные сохранились, необходимо выполнить **commit**. И для наглядности выводим **сообщение**, что данные сохранены.

Для кнопки **btnLoad** (загрузка данных) на обработку нажатия выполним следующие команды:

```
sPref = getPreferences(MODE_PRIVATE);

```

```
String savedText = sPref.getString(SAVED_TEXT, "");

```

```
etText.setText(savedText);

```

```
Toast.makeText(this, "Text loaded", Toast.LENGTH_SHORT).show();

```

Так же, как и **saveText**, с помощью метода **getPreferences** получаем объект **sPref** класса **SharedPreferences**. **MODE\_PRIVATE** снова указывается, хотя и используется только при записи данных. Здесь **Editor** мы не используем, т.к. нас интересует только чтение данных. Читаем с помощью метода **getString** – в параметрах указываем константу - это **имя**, и **значение** по умолчанию (пустая строка). Далее пишем значение в поле ввода **etText** и выводим **сообщение**, что данные считаны.

Таким образом класс MainActivity выглядит следующим образом:

```
import android.support.v7.app.AppCompatActivity;

```

```
import android.content.SharedPreferences;

```

```
import android.content.SharedPreferences.Editor;

```

```
import android.os.Bundle;

```

```
import android.view.View;

```

```
import android.view.View.OnClickListener;

```

```
import android.widget.Button;

```

```
import android.widget.EditText;

```

```
import android.widget.Toast;

```

```
public class MainActivity extends AppCompatActivity implements OnClickListener {

```

```
    EditText etText; //для поля ввода

```

```
    Button btnSave, btnLoad; //для кнопок

```

```
    SharedPreferences sPref; //для работы с Preferences

```

```
    final String SAVED_TEXT = "saved_text"; //имя для сохранения

```

```
    @Override

```

```
    protected void onCreate(Bundle savedInstanceState) {

```

```
        super.onCreate(savedInstanceState);

```

```
        setContentView(R.layout.activity_main);

```

```
        etText = (EditText) findViewById(R.id.etText);

```

```
        btnSave = (Button) findViewById(R.id.btnSave);

```

```
        btnSave.setOnClickListener(this);

```

```
        btnLoad = (Button) findViewById(R.id.btnLoad);

```

```
        btnLoad.setOnClickListener(this);

```

```
    }

```

```
    @Override

```

```
    public void onClick(View v) {

```

```
        switch (v.getId()) {

```

```
            case R.id.btnSave:

```

```
                saveText();

```

```
                break;

```

```
            case R.id.btnLoad:

```

```
                loadText();

```

```
                break;

```

```
            default:

```

```
                break;

```

```
        }

```

```
    }

```

```
    void saveText() {

```

```
        sPref = getPreferences(MODE_PRIVATE);

```

```
        Editor ed = sPref.edit();

```

```
        ed.putString(SAVED_TEXT, etText.getText().toString());

```

```
        ed.commit();

```

```
        Toast.makeText(this, "Text saved", Toast.LENGTH_SHORT).show();

```

```
    }

```

```
    void loadText() {

```

```
        sPref = getPreferences(MODE_PRIVATE);

```

```
        String savedText = sPref.getString(SAVED_TEXT, "");

```

```
        etText.setText(savedText);

```

```
        Toast.makeText(this, "Text loaded", Toast.LENGTH_SHORT).show();

```

```
    }

```

```
}

```

Скриншот приложения, иллюстрирующего данный функционал представлен на рис. 65.

#### **Хранение данных. Работа с файлами**

Еще один способ хранения данных – обычные файлы. В этой теме рассмотрим, как записать/прочесть файл во внутреннюю память и на SD-карту

Разработаем приложение. В нем будет поле для ввода текста и четыре кнопки – **Записать файл**, **Прочесть файл**, **Записать файл на SD** и **Прочесть файл с SD**.

Для начала дополним **strings.xml** следующими строчками:

```
<string name="write_file">Записать файл</string>

```

```
<string name="read_file">Прочесть файл</string>

```

```
<string name="write_file_sd">Записать файл на SD</string>

```

```
<string name="read_file_sd">Прочесть файл с SD</string>

```

Эти переменные заведены для имен кнопок.

Создадим следующий экран:

```
<?хml vеrsion="1.0" еncoding="utf-8"?>

```

```
<LinеarLаyout

```

```
    хmlns:android="httр://schеmas.аndroid.cоm/аpk/rеs/аndroid"

```

```
    android:layout_width="fill_parent"

```

```
    android:layout_height="fill_parent"

```

```
    android:orientation="vertical">

```

```
    <ЕditTеxt

```

```
        android:id="@+id/etText"

```

```
        android:layout_width="match_parent"

```

```
        android:layout_height="wrap_content">

```

```
        <rеquestFоcus>

```

```
        </requestFocus>

```

```
    </ЕditText>

```

```
    <LinеarLаyout

```

```
        android:layout_width="match_parent"

```

```
        android:layout_height="wrap_content">

```

```
        <Вutton

```

```
            android:id="@+id/btnWrite"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="@string/write_file">

```

```
        </Вutton>

```

```
        <Вutton

```

```
            android:id="@+id/btnRead"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="@string/read_file">

```

```
        </Вutton>

```

```
    </LinеarLаyout>

```

```
    <LinеarLаyout

```

```
        android:layout_width="match_parent"

```

```
        android:layout_height="wrap_content">

```

```
        <Вutton

```

```
            android:id="@+id/btnWriteSD"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="@string/write_file_sd">

```

```
        </Вutton>

```

```
        <Вutton

```

```
            android:id="@+id/btnReadSD"

```

```
            android:layout_width="wrap_content"

```

```
            android:layout_height="wrap_content"

```

```
            android:text="@string/read_file_sd">

```

```
        </Вutton>

```

```
    </LinеarLаyout>

```

```
</LinеarLаyout>

```

Поле ввода – **etText**, четыре кнопки – **btnWrite**, **btnRead**, **btnWriteSD** и **btnReadSD**.

В классе **MainActivity** заведем следующие элементы:

```
ЕditTеxt etTеxt; //для поля ввода

```

```
Вutton btnWrite, btnRead, btnWriteSD, btnReadSD; //для кнопок

```

```
final String FILENAME = "file"; //имя файла во внутренней памяти

```

```
final String DIR_SD = "MyFiles"; // имя каталога в памяти SD

```

```
final String FILENAME_SD = "fileSD"; //имя файла в памяти SD

```

В методе **onCreate** для связи элементов класса **MainActivity** с элементами экрана необходимо дописать следующие строчки:

```
еtTеxt = (EditText) findViewById(R.id.etText);

```

```
btnWritе = (Button) findViewById(R.id.btnWrite);

```

```
btnRead = (Button) findViewById(R.id.btnRead);

```

```
btnWriteSD = (Button) findViewById(R.id.btnWriteSD);

```

```
btnReadSD = (Button) findViewById(R.id.btnReadSD);

```

Для кнопки **btnWrite** (записать файл) на обработку нажатия выполним следующие команды:

```
try {

```

```
    // отрываем поток для записи

```

```
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(

```

```
            openFileOutput(FILENAME, MODE_PRIVATE)));

```

```
    // пишем данные

```

```
    bw.write(etText.getText().toString());

```

```
    // закрываем поток

```

```
    bw.close();

```

```
} catch (FileNotFoundExceptione) {

```

```
    e.printStackTrace();

```

```
} catch (IOException e) {

```

```
    e.printStackTrace();}

```

Используется метод **openFileOutput**, который на вход берет имя файла и режим записи: **MODE\_PRIVATE** – файл доступен только этому приложению, **MODE\_WORLD\_READABLE**– файл доступен для чтения всем, **MODE\_WORLD\_WRITEABLE** - файл доступен для записи всем, **MODE\_APPEND** – файл будет дописан, а не начат заново.

Для
кнопки **btnRead** (прочесть файл) на обработку нажатия выполним следующие команды:

```
try {

```

```
    // открываем поток для чтения

```

```
    BufferedReader br = new BufferedReader(new InputStreamReader(

```

```
            openFileInput(FILENAME)));

```

```
    String str = "";

```

```
    // читаем содержимое

```

```
    str = br.readLine();

```

```
    etText.setText(str);

```

```
} catch (FileNotFoundException e) {

```

```
    e.printStackTrace();

```

```
} catch (IOException e) {

```

```
    e.printStackTrace();

```

```
}

```

Используем метод **openFileInput**, принимающий на вход имя файла. Здесь и в методе записи внутреннего файла вы можете задать только имя файла, а каталог для ваших файлов вам уже выделен.

Для кнопки **btnWriteSD** (записать файл на SD) на обработку нажатия выполним следующие команды:

```
// проверяем доступность SD

```

```
if (!Environment.getExternalStorageState().equals(

```

```
        Environment.MEDIA_MOUNTED)) {

```

```
    return;}

```

```
// получаем путь к SD

```

```
File sdPath = Environment.getExternalStorageDirectory();

```

```
// добавляем свой каталог к пути

```

```
sdPath = new File(sdPath.getAbsolutePath() + "/" + DIR_SD);

```

```
// создаем каталог

```

```
sdPath.mkdirs();

```

```
// формируем объект File, который содержит путь к файлу

```

```
File sdFile = new File(sdPath, FILENAME_SD);

```

```
try {

```

```
    // открываем поток для записи

```

```
    BufferedWriter bw = new BufferedWriter(new FileWriter(sdFile));

```

```
    // пишем данные

```

```
    bw.write(etText.getText().toString());

```

```
    // закрываем поток

```

```
    bw.close();

```

```
} catch (IOException e) {

```

```
    e.printStackTrace();}

```

Используем метод **getExternalStorageState** для получения состояния SD-карты. Нам нужно **MEDIA\_MOUNTED** – когда SD-карта вставлена и готова к работе. Далее мы получаем путь к SD-карте (метод **getExternalStorageDirectory**), добавляем свой каталог и имя файла, создаем каталог и пишем данные в файл.

Для кнопки **btnReadSD** (прочесть файл с SD) на обработку нажатия выполним следующие команды:

```
// проверяем доступность SD

```

```
if (!Environment.getExternalStorageState().equals(

```

```
        Environment.MEDIA_MOUNTED)) {

```

```
    return;}

```

```
// получаем путь к SD

```

```
File sdPath = Environment.getExternalStorageDirectory();

```

```
// добавляем свой каталог к пути

```

```
sdPath = new File(sdPath.getAbsolutePath() + "/" + DIR_SD);

```

```
// формируем объект File, который содержит путь к файлу

```

```
File sdFile = new File(sdPath, FILENAME_SD);

```

```
try {

```

```
    // открываем поток для чтения

```

```
    BufferedReader br = new BufferedReader(new FileReader(sdFile));

```

```
    String str = "";

```

```
    // читаем содержимое

```

```
    str = br.readLine();

```

```
    etText.setText(str);

```

```
} catch (FileNotFoundException e) {

```

```
    e.printStackTrace();

```

```
} catch (IOException e) {

```

```
    e.printStackTrace();}

```

Все аналогично предыдущему методу, только файл не пишем, а читаем.

Для успешной работы приложения, осталось в манифест добавить разрешение на работу с файлами на SD - android.permission.WRITE\_EXTERNAL\_STORAGE.

В общем, при работе с файлами на SD вы используете стандартные java механизмы. А при работе с внутренним хранилищем для удобства можно использовать методы-оболочки от Activity:

* **openFileOutput** – открыть файл на запись
* **openFileInput** – открыть файл на чтение
* **deleteFile** – удалить файл

Скриншот приложения,
иллюстрирующего данный функционал представлен на рис. 66.

#### **Создание приложения для хранения списка класса**

Необходимо создать проект, в котором предусмотрена возможность работать со списком класса. Предусмотреть возможность сохранения
списка, возможность добавления ученика, удаления ученика по заданному ФИО. Для хранения данных использовать обычные файлы. Примерный вариант формы приложения представлен на рис. 67.

#### **Введение в базы данных, основы языка SQL**

Возможно, вы еще не знаете, что входит в понятие базы данных, но то, что вы ими постоянно пользуетесь абсолютно точно. Каждый раз, когда вы что-то ищете в поисковике, вы используете базу данных. Когда вы вводите свои логин и пароль для входа на какой-нибудь сервис, они сравниваются со значениями, которые хранятся в базе данных этого сервиса.

Давайте разберемся с терминами и понятиями баз данных:

**База данных** - набор сведений, хранящихся некоторым упорядоченным способом. Можно сравнить базу данных со шкафом, в котором хранятся документы. Иными словами, база данных - это хранилище данных. Сами по себе базы данных не представляли бы интереса, если бы не было систем управления базами данных (СУБД).

**Система управления базами данных** - это совокупность языковых и программных средств, которая осуществляет доступ к данным, позволяет их создавать, менять и удалять, обеспечивает безопасность данных и т.д. В общем СУБД - это система, позволяющая создавать базы данных и манипулировать сведениями из них. А осуществляет этот доступ к данным СУБД посредством специального языка - SQL.

**SQL** - язык структурированных запросов, основной задачей которого является предоставление простого способа считывания и записи информации в базу данных.

Не вдаваясь далее в подробности, отметим, что на сегодняшний день число используемых СУБД исчисляется десятками. Наиболее известные однопользовательские СУБД - Microsoft Visual FoxPro и Access, многопользовательские - MS SQL Server, Oracle и MySQL.

Создавая базу данных, мы стремимся упорядочить информацию по различным признакам для того, чтобы потом извлекать из нее необходимые нам данные в любом сочетании. Сделать это возможно, только если данные структурированы. Структурирование - это набор соглашений о способах представления данных. Понятно, что структурировать информацию можно по-разному. В зависимости от структуры различают **иерархическую**, **сетевую**, **реляционную**, **объектно-ориентированную** и **гибридную** модели баз данных. Самой популярной на сегодняшний день является реляционная структура, поэтому об остальных упомянем лишь вскользь.

**Иерархическая структура базы данных**

Это древовидная структура представления информации. Ее особенность в том, что каждый узел на более низком уровне имеет связь только с одним узлом на более высоком уровне. Посмотрим, например, на фрагмент иерархической структуры базы данных "Институт", рис. 68.

Из структуры понятно, что на одной кафедре может работать несколько преподавателей. Такая связь называется "один ко многим" (одна кафедра - много преподавателей). Но если мы попытаемся добавить в эту структуру группы студентов, то нам понадобится связь "многие ко многим", рис. 69.

    (один преподаватель может работать со многими группами, а одна группа может учиться у многих преподавателей), а такой связи в иерархической структуре быть не может (т.к. связь может быть только с одним узлом на более высоком уровне). Это основной недостаток подобной структуры базы данных.

**Сетевая структура базы данных**

По сути, это расширение иерархической структуры. Все то же самое, но существует связь "многие ко многим". Сетевая структура базы данных позволяет нам добавить группы в наш пример. Недостатком сетевой модели является сложность разработки серьезных приложений.

**Реляционная структура базы данных**

Все данные представлены в виде простых таблиц, разбитых на строки и столбцы, на пересечении которых расположены данные. Подробно об этом мы будем говорить в следующих уроках, здесь же хочется отметить, что эта структура стала настоящим прорывом в развитии баз данных.

**Объектно-ориентированные и гибридные базы данных**

В объектно-ориентированных базах данных данные хранятся в виде объектов, что очень удобно. Но на сегодняшний день такие БД еще распространенны, т.к. уступают в производительности реляционным.

**Гибридные БД** совмещают в себе возможности реляционных и объектно-ориентированных, поэтому их часто называют объектно-реляционными. Примером такой СУБД является Oracle, начиная с восьмой версии.

**Реляционные базы данных**, как мы уже знаем, состоят из таблиц. Каждая таблица состоит из столбцов (их называют полями или атрибутами) и строк (их называют записями или кортежами). Таблицы в реляционных базах данных обладают рядом свойств. Основными являются следующие:

* В таблице не может быть двух одинаковых строк. В математике таблицы, обладающие таким свойством, называют отношениями - по-английски relation, отсюда и название - реляционные.
* Столбцы располагаются в определенном порядке, который создается при создании таблицы. В таблице может не быть ни одной строки, но обязательно должен быть хотя бы один столбец.
* У каждого столбца есть уникальное имя (в пределах таблицы), и все значения в одном столбце имеют один тип (число, текст, дата...).
* На пересечении каждого столбца и строки может находиться только атомарное значение (одно значение, не состоящее из группы значений). Таблицы, удовлетворяющие этому условию, называют нормализованными.

Предположим, мы захотели создать базу данных для форума. У форума есть зарегистрированные пользователи, которые создают темы и оставляют сообщения в этих темах. Эта информация и должна храниться в базе данных.

Теоретически (на бумаге) мы можем все это расположить в одной таблице, рис. 70.

Но это противоречит свойству атомарности (одно значение в одной ячейке), а в столбцах Темы и Сообщения у нас предполагается неограниченное количество значений. Значит, нашу таблицу надо разбить на три: Пользователи, Темы и Сообщения, рис 71.

    Наша таблица Пользователи удовлетворяет всем условиям. А вот таблицы Темы и Сообщения - нет. Ведь в таблице не может быть двух одинаковых строк, а где гарантия, что один пользователь не оставит два одинаковых сообщения, рис. 72.

    Кроме того, мы знаем, что каждое сообщение обязательно относится к какой-либо теме. А как это можно узнать из наших таблиц? Никак. Для решения этих проблем, в реляционных базах данных существуют **ключи**.

**Первичный ключ** (сокращенно РК - primary key) - столбец, значения которого во всех строках различны. Первичные ключи могут быть логическими (естественными) и суррогатными (искусственными). Так, для нашей таблицы Пользователи первичным ключом может стать столбец e-mail (ведь теоретически не может быть двух пользователей с одинаковым e-mail). На практике лучше использовать суррогатные ключи, т.к. их применение позволяет абстрагировать ключи от реальных данных. Кроме того, первичные ключи менять нельзя, а что если у пользователя сменится e-mail?

**Суррогатный ключ** представляет собой дополнительное поле в базе данных. Как правило, это порядковый номер записи (хотя вы можете задавать их на свое усмотрение, контролируя, чтобы они были уникальны). Давайте внесем поля первичных ключей в наши таблицы, рис. 73-75.

Теперь каждая запись в наших таблицах уникальна. Нам осталось установить соответствие между темами и сообщениями в них. Делается это также при помощи первичных ключей. В таблицу сообщения мы добавим еще одно поле, рис. 76.

Теперь понятно, что сообщение с id=2 принадлежит теме "О рыбалке" (id темы = 4), созданной Васей, а остальные сообщения принадлежать теме "О рыбалке" (id темы = 1), созданной Кириллом. Такое поле называется **внешний ключ** (сокращенно FK - foreign key). Каждое значение этого
поля соответствует какому-либо первичному ключу из таблицы "Темы". Так устанавливается однозначное соответствие между сообщениями и темами, к которым они относятся.

Последний нюанс. Предположим, у нас добавился новый пользователь, и зовут его тоже Вася, рис. 77.

Как мы узнаем, какой
именно Вася оставил сообщения? Для этого поля автор в таблицах "Темы" и "Сообщения" мы сделаем также внешними ключами, рис. 78-79.

Наша база данных готова. Схематично ее можно представить, как на рис. 80.

    **SQL**

Для создания базы данных в SQL существует оператор create database. Создание базы данных имеет следующий синтаксис:

create database имя\_базы\_данных;

Для создания таблиц в SQL существует оператор create table. Создание базы данных имеет следующий синтаксис:

create table имя\_таблицы (имя\_первого\_столбца тип, имя\_второго\_столбца тип, ..., имя\_последнего\_столбца тип );

Для получения данных в SQL существует оператор SELECT. Синтаксис его использования следующий:

SELECT что\_выбрать FROM откуда\_выбрать;

Для получения данных с сортировкой данных в SQL существует ключевое слово ORDER BY после которого указывается имя столбца по которому будет происходить сортировка. Синтаксис следующий:

SELECT имя\_столбца FROM имя\_таблицы ORDER BY имя\_столбца\_сортировки;

По умолчанию сортировка идет по возрастанию, но это можно изменить, добавив ключевое слово DESC.

Для получения данных с фильтрацией данных в SQL существует ключевое слово WHERE после которого указывается условие по которому будет происходить сортировка. Синтаксис следующий:

SELECT имя\_столбца FROM имя\_таблицы WHERE условие;

Для добавления данных служит оператор INSERT. Его синтаксис следующий:

INSERT INTO имя\_таблицы VALUES ('значение\_первого\_столбца','значение\_второго\_столбца', ..., 'значение\_последнего\_столбца');

Для обновления уже существующих данных служит оператор UPDATE. Его синтаксис следующий:

UPDATE имя\_таблицы SET имя\_столбца=значение\_столбца WHERE условие;

Для удаления данных служит оператор DELETE. Его синтаксис следующий:

DELETE FROM имя\_таблицы WHERE условие;

#### **Хранение данных. SQLite**

Мы рассмотрели 2 способа хранения данных, но способы эти достаточно ограничены и для хранения большого количества структурированных данных неудобны. Рассмотрим еще один способ – SQLite. Это база данных с таблицами и запросами - все как в обычных БД.

Для начала, немного теории по взаимодействию приложения и БД.

В приложении, при подключении к БД мы указываем имя БД и версию. При этом могут возникнуть следующие ситуации:

1) БД не существует. Это может быть, например, в случае первичной установки программы. В этом случае приложение должно само создать БД и все таблицы в ней. И далее оно уже работает с только что созданной БД.

2) БД существует, но ее версия устарела. Это может быть в случае обновления программы. Например, новой версии программы нужны дополнительные поля в старых таблицах или новые таблицы. В этом случае приложение должно апдейтить существующие таблицы и создать новые, если это необходимо.

3) БД существует и ее версия актуальна. В этом случае приложение успешно подключается к БД и работает.

Как вы понимаете, фраза "приложение должно" равнозначна фразе "разработчик должен", т.е. это наша задача. Для обработки описанных выше ситуаций нам надо создать класс, являющийся наследником для **SQLiteOpenHelper**. Назовем его **DBHelper**. Этот класс предоставит нам методы для создания или обновления БД в случаях ее отсутствия или устаревания.

**onCreate** - метод, который будет вызван, если БД, к которой мы хотим подключиться – не существует

**onUpgrade** - будет вызван в случае, если мы пытаемся подключиться к БД более новой версии, чем существующая

Создадим простое приложение – справочник контактов, которое будет хранить имя и email. Вводить данные будем на экране приложения, а для отображения информации используем текстовое поле.

Создадим для начала наш класс DBHelper, который имеет вид:

```
import android.content.Context;

```

```
import android.database.sqlite.SQLiteDatabase;

```

```
import android.database.sqlite.SQLiteOpenHelper;

```

```
public class DBHelper extends SQLiteOpenHelper {

```

```
    public DBHelper(Context context) {

```

```
        // конструктор суперкласса

```

```
        super(context, "myDB", null, 1);

```

```
    }

```

```
    @Override

```

```
    public void onCreate(SQLiteDatabase db) {

```

```
        db.execSQL("create table mytable ("

```

```
                + "id integer primary key autoincrement,"

```

```
                + "name text,"

```

```
                + "email text" + ");");  }

```

```
    @Override

```

```
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}  }

```

В конструкторе мы вызываем конструктор суперкласса и передаем ему:

**context** - контекст

**mydb** - название базы данных

**null** – объект для работы с курсорами, нам пока не нужен, поэтому null

**1** – версия базы данных

В методе **onCreate** этого класса мы используем метод **execSQL** объекта **SQLiteDatabase** для выполнения SQL-запроса, который создает таблицу. Напомню – этот метод вызывается, если БД не существует и ее надо создавать. По запросу видно, что мы создаем таблицу **mytable** с полями **id**, **name** и **email**.

Метод **onUpgrade** пока не заполняем, т.к. используем одну версию БД и менять ее не планируем.

Чтобы **добавить** новый элемент в таблицу необходимо:

```
// создать объект класса DBHelper

```

```
DBHelper dbHelper = new DBHelper(this);

```

```
// подключиться к БД

```

```
SQLiteDatabase db = dbHelper.getWritableDatabase();

```

```
// создать объект для данных

```

```
ContentValues cv = new ContentValues();

```

```
// подготовим данные для вставки в виде пар: наименование столбца - значение

```

```
cv.put("name", "Иванов");

```

```
cv.put("email", "ivanov@mail.ru");

```

```
// вставить запись и получить ее ID

```

```
long rowID = db.insert("mytable", null, cv);

```

```
// закрыть подключение к БД

```

```
dbHelper.close();

```

Чтобы **удалить** все элементы из таблицы необходимо:

```
// создать объект класса DBHelper

```

```
DBHelper dbHelper = new DBHelper(this);

```

```
// подключиться к БД

```

```
SQLiteDatabase db = dbHelper.getWritableDatabase();

```

```
// удалить все записи

```

```
int clearCount = db.delete("mytable", null, null);

```

```
// закрыть подключение к БД

```

```
dbHelper.close();

```

Чтобы **получить** все элементы из таблицы необходимо:

```
// создать объект класса DBHelper

```

```
DBHelper dbHelper = new DBHelper(this);

```

```
// подключиться к БД

```

```
SQLiteDatabase db = dbHelper.getWritableDatabase();

```

```
// делаем запрос всех данных из таблицы mytable, получаем Cursor

```

```
Cursor c = db.query("mytable", null, null, null, null, null, null);

```

```
// ставим позицию курсора на первую строку выборки

```

```
// если в выборке нет строк, вернется false

```

```
if (c.moveToFirst()) {

```

```
    // определяем номера столбцов по имени в выборке

```

```
    int idColIndex = c.getColumnIndex("id");

```

```
    int nameColIndex = c.getColumnIndex("name");

```

```
    int emailColIndex = c.getColumnIndex("email");

```

```
    do {

```

```
        // получаем значения текущей записи по номерам столбцов

```

```
        String str =

```

```
                "ID = " + c.getInt(idColIndex) +

```

```
                        ", name = " + c.getString(nameColIndex) +

```

```
                        ", email = " + c.getString(emailColIndex);

```

```
        // переход на следующую строку

```

```
        // а если следующей нет (текущая - последняя), то false - выходим из цикла

```

```
    } while (c.moveToNext());}

```

```
// закрыть подключение к БД

```

```
dbHelper.close();

```

Скриншот приложения, иллюстрирующего данный функционал представлен на рис. 81.

#### **Компоненты Spinner и ListView, для работы с базой данных**

Компоненты Spinner и ListView очень удобно использовать для отображения данных из базы данных. Компонент Spinner представляет собой выпадающий список, а компонент ListView представляет собой просто список. Эти компоненты можно легко найти в панели где располагаются компонентами, такие как кнопка, поле ввода и т.д.

Изучая основы работы с базой данных SQLite, нам требовалось создать класс DBHelper, в этом примере проделываем то же самое. Наш класс DBHelper будет иметь вид:

```
import android.content.ContentValues;

```

```
import android.content.Context;

```

```
import android.database.sqlite.SQLiteDatabase;

```

```
import android.database.sqlite.SQLiteOpenHelper;

```

```
public class DBHelper extends SQLiteOpenHelper {

```

```
    public DBHelper(Context context) {

```

```
        // конструктор суперкласса

```

```
        super(context, "myDB", null, 1);

```

```
    }

```

```
    @Override

```

```
    public void onCreate(SQLiteDatabase db) {

```

```
        // создаем таблицу с полями

```

```
        db.execSQL("create table mytable ("

```

```
               + "_id integer primary key autoincrement,"

```

```
               + "name text,"

```

```
               + "email text" + ");");

```

```
        ContentValues cv = new ContentValues();

```

```
        // подготовим данные для вставки в виде пар: наименование столбца - значение

```

```
        cv.put("name", "Иванов");

```

```
        cv.put("email", "ivanov@mail.ru");

```

```
        // вставить запись

```

```
        db.insert("mytable", null, cv);

```

```
       // подготовим данные для вставки в виде пар: наименование столбца - значение

```

```
        cv.put("name", "Петров");

```

```
        cv.put("email", "petrov@mail.ru");

```

```
        // вставить запись

```

```
        db.insert("mytable", null, cv);

```

```
       // подготовим данные для вставки в виде пар: наименование столбца - значение

```

```
        cv.put("name", "Сидоров");

```

```
        cv.put("email", "sidorov@mail.ru");

```

```
        // вставить запись

```

```
        db.insert("mytable", null, cv);

```

```
   }

```

```
    @Override

```

```
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){} }

```

Как видно из этого класса, в нашей базе одна таблица с именем mytable и тремя полями \_id, name и email. Обратите внимание на имя ключевого поля, его стоит называть именно \_id, поскольку компоненты Spinner и ListView реализованы так, что они не воспринимают ключевое поле с другим именем. Как видно из класса, в таблицу при создании сразу заносится 3 записи. Это позволяет изначально работать не с пустой таблицей.

**Компонент Spinner**

Давайте расположим на экране компонент Spinner  с именем spnTest.

Далее для загрузки данных в наш компонент из базы данных, необходимо выполнить следующий код:

```
// создаем объект класса Spinner

```

```
// привязываем к нему компонент Spinner с экрана

```

```
Spinner spinnerTest = (Spinner) findViewById(R.id.spnTest);

```

```
// создаем объект класса DBHelper

```

```
DBHelper dbHelper = new DBHelper(this);

```

```
// подключаемся к БД

```

```
SQLiteDatabase db = dbHelper.getWritableDatabase();

```

```
// делаем запрос к БД, в котором возвращаем не все поля

```

```
// а только поля _id и name

```

```
Cursor cursorSpinner =  db.query("mytable", new String[] {"_id", "name"}, null, null, null, null, null);

```

```
// создаем массив строк с именем поля для отображения

```

```
String[] columnsSpinner = new String[] {"name"};

```

```
// создаем массив с идентификатором текстовой метки

```

```
int[] toSpinner = new int[] {android.R.id.text1};

```

```
// создаем объект класса SimpleCursorAdapter

```

```
// android.R.layout.simple_spinner_dropdown_item

```

```
// служит шаблоном для раскрывающего списка

```

```
SimpleCursorAdapter adapterSpinner = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, cursorSpinner, columnsSpinner, toSpinner, 0);

```

```
// устанавливаем в наш Spinner

```

```
// созданных ранее объект класса SimpleCursorAdapter

```

```
spinnerTest.setAdapter(adapterSpinner);

```

```
// закрываем БД

```

```
dbHelper.close();

```

После выполнения этого кода в нашем компоненте Spinner будет содержаться информация по всем записям из базы данных и отображаться значения поля name.

Для получения информации о выбранном элементе можно использовать следующие команды:

```
// получение значение выбранного элемента

```

```
spinnerTest.getSelectedItem()

```

```
// получение значение поля _id выбранного элемента

```

```
spinnerTest.getSelectedItemId()

```

**Компонент ListView**

Давайте расположим на экране компонент **ListView** с именем spnTest.

Далее для загрузки данных в наш компонент из базы данных, необходимо выполнить следующий код:

```
// создаем объект класса ListView

```

```
// привязываем к нему компонент ListView с экрана

```

```
ListView listTest = (ListView) findViewById(R.id.lTest);

```

```
// создаем объект класса DBHelper

```

```
DBHelper dbHelper = new DBHelper(this);

```

```
// подключаемся к БД

```

```
SQLiteDatabase db = dbHelper.getWritableDatabase();

```

```
// делаем запрос к БД, в котором возвращаем поля _id, name и email

```

```
Cursor cursorList =  db.query("mytable", new String[] {"_id", "name", "email"}, null, null, null, null, null);

```

```
// создаем массив строк с именами полей для отображения

```

```
String[] columnsList = new String[] {"name", "email"};

```

```
// создаем массив с идентификаторами текстовых меток

```

```
// большой и маленький текст для отображения в ListView

```

```
int[] toList = new int[] {android.R.id.text1, android.R.id.text2};

```

```
// создаем объект класса SimpleCursorAdapter

```

```
// android.R.layout.two_line_list_item служит шаблоном для списка,

```

```
// с отображением двух значений можно использовать и другой шаблон, для

```

```
// отображения только одного значения можно посмотреть пример про Spinner

```

```
SimpleCursorAdapter adapterList = new SimpleCursorAdapter(this, android.R.layout.two_line_list_item, cursorList, columnsList, toList, 0);

```

```
listTest.setAdapter(adapterList);

```

```
// закрываем БД

```

```
dbHelper.close();

```

После выполнения этого кода в нашем компоненте ListView будет содержаться информация по всем записям из базы данных и отображаться значения поля nameбольшим шрифтом и значение поля email маленьким шрифтом.

Для получения информации о выбранном элементе можно использовать следующие команды:

```
// получение значение выбранного элемента

```

```
listTest.getSelectedItem()

```

```
// получение значение поля _id выбранного элемента

```

```
listTest.getSelectedItemId()

```

Скриншот приложения, иллюстрирующего данный функционал представлен на рис. 82.