Рассмотрим, как мы можем создать в приложении на Android динамический поиск по базе данных SQLite.
Итак, создадим новый проект с пустой MainActivity. Для этого проекта возьмем базу данных из прошлой темы (или создадим новую). Данная база данных называется cityinfo и имеет одну таблицу users с тремя полями _id, name, age:
CREATE TABLE `users` ( `_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `name` TEXT NOT NULL, `year` INTEGER NOT NULL );
И также добавим в проект в Android Studio папку assets, а в папку assets - только что созданную базу данных:
В моем случае база данных называется "cityinfo.db".
Как показано выше на скриншоте, добавив в проект в одну папку с MainActivity новый класс DatabaseHelper:
package com.example.livesearchapp; import android.database.SQLException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteDatabase; import android.content.Context; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; class DatabaseHelper extends SQLiteOpenHelper { private static String DB_PATH; // полный путь к базе данных private static String DB_NAME = "cityinfo.db"; private static final int SCHEMA = 1; // версия базы данных static final String TABLE = "users"; // название таблицы в бд // названия столбцов static final String COLUMN_ID = "_id"; static final String COLUMN_NAME = "name"; static final String COLUMN_YEAR = "year"; private Context myContext; DatabaseHelper(Context context) { super(context, DB_NAME, null, SCHEMA); this.myContext=context; DB_PATH =context.getFilesDir().getPath() + DB_NAME; } @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } void create_db(){ File file = new File(DB_PATH); if (!file.exists()) { //получаем локальную бд как поток try(InputStream myInput = myContext.getAssets().open(DB_NAME); // Открываем пустую бд OutputStream myOutput = new FileOutputStream(DB_PATH)) { // побайтово копируем данные byte[] buffer = new byte[1024]; int length; while ((length = myInput.read(buffer)) > 0) { myOutput.write(buffer, 0, length); } myOutput.flush(); } catch(IOException ex){ Log.d("DatabaseHelper", ex.getMessage()); } } } public SQLiteDatabase open()throws SQLException { return SQLiteDatabase.openDatabase(DB_PATH, null, SQLiteDatabase.OPEN_READWRITE); } }
Перейдем к файлу activity_main.xml, который определяет визуальный интерфейс, и изменим его следующим образом:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/userFilter" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Поиск" app:layout_constraintBottom_toTopOf="@+id/userList" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ListView android:id="@+id/userList" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/userFilter" /> </androidx.constraintlayout.widget.ConstraintLayout>
Итак, у нас будет элемент ListView для отображения списка и текстовое поле для фильтрации.
Теперь изменим код MainActivity:
package com.example.livesearchapp; import androidx.appcompat.app.AppCompatActivity; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.widget.EditText; import android.widget.FilterQueryProvider; import android.widget.ListView; import android.widget.SimpleCursorAdapter; public class MainActivity extends AppCompatActivity { DatabaseHelper sqlHelper; SQLiteDatabase db; Cursor userCursor; SimpleCursorAdapter userAdapter; ListView userList; EditText userFilter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); userList = (ListView)findViewById(R.id.userList); userFilter = (EditText)findViewById(R.id.userFilter); sqlHelper = new DatabaseHelper(getApplicationContext()); // создаем базу данных sqlHelper.create_db(); } @Override public void onResume() { super.onResume(); try { db = sqlHelper.open(); userCursor = db.rawQuery("select * from " + DatabaseHelper.TABLE, null); String[] headers = new String[]{DatabaseHelper.COLUMN_NAME, DatabaseHelper.COLUMN_YEAR}; userAdapter = new SimpleCursorAdapter(this, android.R.layout.two_line_list_item, userCursor, headers, new int[]{android.R.id.text1, android.R.id.text2}, 0); // если в текстовом поле есть текст, выполняем фильтрацию // данная проверка нужна при переходе от одной ориентации экрана к другой if(!userFilter.getText().toString().isEmpty()) userAdapter.getFilter().filter(userFilter.getText().toString()); // установка слушателя изменения текста userFilter.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } // при изменении текста выполняем фильтрацию public void onTextChanged(CharSequence s, int start, int before, int count) { userAdapter.getFilter().filter(s.toString()); } }); // устанавливаем провайдер фильтрации userAdapter.setFilterQueryProvider(new FilterQueryProvider() { @Override public Cursor runQuery(CharSequence constraint) { if (constraint == null || constraint.length() == 0) { return db.rawQuery("select * from " + DatabaseHelper.TABLE, null); } else { return db.rawQuery("select * from " + DatabaseHelper.TABLE + " where " + DatabaseHelper.COLUMN_NAME + " like ?", new String[]{"%" + constraint.toString() + "%"}); } } }); userList.setAdapter(userAdapter); } catch (SQLException ex){} } @Override public void onDestroy(){ super.onDestroy(); // Закрываем подключение и курсор db.close(); userCursor.close(); } }
Прежде всего надо отметить, что для фильтрации данных в адаптере, нам надо получить фильтр адаптера, а у этого фильтра выполнить метод filter()
:
userAdapter.getFilter().filter(s.toString());
В этот метод filter()
передается ключ поиска.
Для текстового поля мы можем отслеживать изменения содержимого с помощью слушателя:
userFilter.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } // при изменении текста выполняем фильтрацию public void onTextChanged(CharSequence s, int start, int before, int count) { userAdapter.getFilter().filter(s.toString()); } });
В слушателе TextWatcher в методе onTextChanged
как раз и вызывается метод filter()
, в который передется введенная пользователем в текстовое поле
последовательность символов.
Сам вызызов метода filter()
мало на что влияет. Нам нало еще определить провайдер фильтрации адаптера, которые и будет инкапсулировать реальную логику фильтрации:
userAdapter.setFilterQueryProvider(new FilterQueryProvider() { @Override public Cursor runQuery(CharSequence constraint) { if (constraint == null || constraint.length() == 0) { return db.rawQuery("select * from " + DatabaseHelper.TABLE, null); } else { return db.rawQuery("select * from " + DatabaseHelper.TABLE + " where " + DatabaseHelper.COLUMN_NAME + " like ?", new String[]{"%" + constraint.toString() + "%"}); } } });
Сущность этого провайдера заключается в выполнении SQL-выражений к бд, а именно конструкций "select from" и "select from where like". Данные простейшие выражения выполняют регистрозависимую фильтрацию. В результате адаптаре получает отфильтрованные данные.
Следует также отметить следующий код:
if(!userFilter.getText().toString().isEmpty()) userAdapter.getFilter().filter(userFilter.getText().toString());
Данный код нам нужен при смене ориентации (например, с портретной на альбомную). И если ориентация устройства изменена, но в текстовом поле все же есть некоторые текст-фильтр, то выполняется фильтрация. Иначе бы она не выполнялась.
И после запуска мы сможем насладиться фильтрацией данных: