Динамічний пошук по базі даних SQLite
Розглянемо, як ми можемо створити в додатку на 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());
Цей код потрібен нам при зміні орієнтації (наприклад, з портретної на альбомну). І якщо орієнтація пристрою змінена, але в текстовому полі все ж є якийсь текст-фільтр, то виконується фільтрація. Інакше фільтрація не виконується.
І після запуску ми зможемо насолодитися фільтрацією даних:
