Cómo almacenar y buscar datos

Hay muchas formas de almacenar tus datos, como en una base de datos en línea, en un SQLite local en una base de datos o en un archivo de texto. Depende de ti decidir cuál es la mejor solución para tu y mantener la integridad de su aplicación. En esta lección, se muestra cómo crear una tabla virtual SQLite que proporcione datos búsqueda en el texto completo. La tabla se completa con datos de un archivo de texto que contiene una palabra y par de definiciones en cada línea del archivo.

Cómo crear la tabla virtual

Una tabla virtual se comporta de manera similar a una tabla SQLite, pero lee y escribe en un objeto en a través de devoluciones de llamada, en lugar de a un archivo de base de datos. Para crear una tabla virtual, crea una clase para la tabla:

Kotlin

class DatabaseTable(context: Context) {

    private val databaseOpenHelper = DatabaseOpenHelper(context)

}

Java

public class DatabaseTable {
    private final DatabaseOpenHelper databaseOpenHelper;

    public DatabaseTable(Context context) {
        databaseOpenHelper = new DatabaseOpenHelper(context);
    }
}

Crea una clase interna en la DatabaseTable que extienda el SQLiteOpenHelper. La clase SQLiteOpenHelper define métodos abstractos que debes anular para que la tabla de tu base de datos se pueda crear y se actualicen cuando sea necesario. Por ejemplo, aquí hay un código que declara una tabla de base de datos que contienen palabras para una aplicación de diccionario:

Kotlin

private const val TAG = "DictionaryDatabase"

// The columns we'll include in the dictionary table
const val COL_WORD = "WORD"
const val COL_DEFINITION = "DEFINITION"

private const val DATABASE_NAME = "DICTIONARY"
private const val FTS_VIRTUAL_TABLE = "FTS"
private const val DATABASE_VERSION = 1

private const val FTS_TABLE_CREATE =
        "CREATE VIRTUAL TABLE $FTS_VIRTUAL_TABLE USING fts3 ($COL_WORD, $COL_DEFINITION)"

class DatabaseTable(context: Context) {

    private val databaseOpenHelper: DatabaseOpenHelper

    init {
        databaseOpenHelper = DatabaseOpenHelper(context)
    }

    private class DatabaseOpenHelper internal constructor(private val helperContext: Context) :
            SQLiteOpenHelper(helperContext, DATABASE_NAME, null, DATABASE_VERSION) {
        private lateinit var mDatabase: SQLiteDatabase

        override fun onCreate(db: SQLiteDatabase) {
            mDatabase = db
            mDatabase.execSQL(FTS_TABLE_CREATE)
        }

        override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
            Log.w(
                    TAG,
                    "Upgrading database from version $oldVersion to $newVersion , which will " +
                            "destroy all old data"
            )

            db.execSQL("DROP TABLE IF EXISTS $FTS_VIRTUAL_TABLE")
            onCreate(db)
        }

    }
}

Java

public class DatabaseTable {

    private static final String TAG = "DictionaryDatabase";

    // The columns we'll include in the dictionary table
    public static final String COL_WORD = "WORD";
    public static final String COL_DEFINITION = "DEFINITION";

    private static final String DATABASE_NAME = "DICTIONARY";
    private static final String FTS_VIRTUAL_TABLE = "FTS";
    private static final int DATABASE_VERSION = 1;

    private final DatabaseOpenHelper databaseOpenHelper;

    public DatabaseTable(Context context) {
        databaseOpenHelper = new DatabaseOpenHelper(context);
    }

    private static class DatabaseOpenHelper extends SQLiteOpenHelper {

        private final Context helperContext;
        private SQLiteDatabase mDatabase;

        private static final String FTS_TABLE_CREATE =
                    "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
                    " USING fts3 (" +
                    COL_WORD + ", " +
                    COL_DEFINITION + ")";

        DatabaseOpenHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
            helperContext = context;
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            mDatabase = db;
            mDatabase.execSQL(FTS_TABLE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                    + newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
            onCreate(db);
        }
    }
}

Cómo completar la tabla virtual

La tabla ahora necesita datos para almacenar. El siguiente código muestra cómo leer un archivo de texto (ubicado en res/raw/definitions.txt) que contiene palabras y sus definiciones, cómo para analizar ese archivo y cómo insertar cada línea de ese archivo como una fila en la tabla virtual. Esta se realiza en otro subproceso para evitar que se bloquee la IU. Agrega el siguiente código a tu Clase interna DatabaseOpenHelper.

Sugerencia: Es posible que también quieras configurar una devolución de llamada para notificar a tu IU de la finalización de este subproceso.

Kotlin

private fun loadDictionary() {
    Thread(Runnable {
        try {
            loadWords()
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }).start()
}

@Throws(IOException::class)
private fun loadWords() {
    val inputStream = helperContext.resources.openRawResource(R.raw.definitions)

    BufferedReader(InputStreamReader(inputStream)).use { reader ->
        var line: String? = reader.readLine()
        while (line != null) {
            val strings: List<String> = line.split("-").map { it.trim() }
            if (strings.size < 2) continue
            val id = addWord(strings[0], strings[1])
            if (id < 0) {
                Log.e(TAG, "unable to add word: ${strings[0]}")
            }
            line = reader.readLine()
        }
    }
}

fun addWord(word: String, definition: String): Long {
    val initialValues = ContentValues().apply {
        put(COL_WORD, word)
        put(COL_DEFINITION, definition)
    }

    return database.insert(FTS_VIRTUAL_TABLE, null, initialValues)
}

Java

private void loadDictionary() {
        new Thread(new Runnable() {
            public void run() {
                try {
                    loadWords();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

private void loadWords() throws IOException {
    final Resources resources = helperContext.getResources();
    InputStream inputStream = resources.openRawResource(R.raw.definitions);
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

    try {
        String line;
        while ((line = reader.readLine()) != null) {
            String[] strings = TextUtils.split(line, "-");
            if (strings.length < 2) continue;
            long id = addWord(strings[0].trim(), strings[1].trim());
            if (id < 0) {
                Log.e(TAG, "unable to add word: " + strings[0].trim());
            }
        }
    } finally {
        reader.close();
    }
}

public long addWord(String word, String definition) {
    ContentValues initialValues = new ContentValues();
    initialValues.put(COL_WORD, word);
    initialValues.put(COL_DEFINITION, definition);

    return database.insert(FTS_VIRTUAL_TABLE, null, initialValues);
}

Llama al método loadDictionary() cuando corresponda para propagar la tabla. R un buen lugar sería en onCreate() de la clase DatabaseOpenHelper, justo después de crear la tabla:

Kotlin

override fun onCreate(db: SQLiteDatabase) {
    database = db
    database.execSQL(FTS_TABLE_CREATE)
    loadDictionary()
}

Java

@Override
public void onCreate(SQLiteDatabase db) {
    database = db;
    database.execSQL(FTS_TABLE_CREATE);
    loadDictionary();
}

Una vez que hayas creado y propagado la tabla virtual, usa la búsqueda que proporciona la SearchView para encontrar los datos. Agrega los siguientes métodos a la DatabaseTable para crear una instrucción de SQL que busque la consulta:

Kotlin

fun getWordMatches(query: String, columns: Array<String>?): Cursor? {
    val selection = "$COL_WORD MATCH ?"
    val selectionArgs = arrayOf("$query*")

    return query(selection, selectionArgs, columns)
}

private fun query(
        selection: String,
        selectionArgs: Array<String>,
        columns: Array<String>?
): Cursor? {
    val cursor: Cursor? = SQLiteQueryBuilder().run {
        tables = FTS_VIRTUAL_TABLE
        query(databaseOpenHelper.readableDatabase,
                columns, selection, selectionArgs, null, null, null)
    }

    return cursor?.run {
        if (!moveToFirst()) {
            close()
            null
        } else {
            this
        }
    } ?: null
}

Java

public Cursor getWordMatches(String query, String[] columns) {
    String selection = COL_WORD + " MATCH ?";
    String[] selectionArgs = new String[] {query+"*"};

    return query(selection, selectionArgs, columns);
}

private Cursor query(String selection, String[] selectionArgs, String[] columns) {
    SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
    builder.setTables(FTS_VIRTUAL_TABLE);

    Cursor cursor = builder.query(databaseOpenHelper.getReadableDatabase(),
            columns, selection, selectionArgs, null, null, null);

    if (cursor == null) {
        return null;
    } else if (!cursor.moveToFirst()) {
        cursor.close();
        return null;
    }
    return cursor;
}

Llama a getWordMatches() para encontrar la búsqueda. Se muestran todos los resultados que coinciden. En un Cursor que puedes iterar o usar para compilar un ListView. En este ejemplo, se llama a getWordMatches() en el método handleIntent() de la función de búsqueda. actividad. Recuerda que la actividad de búsqueda recibe la consulta dentro del intent ACTION_SEARCH como un extra, debido al filtro de intents que creados previamente:

Kotlin

private val db = DatabaseTable(this)

...

private fun handleIntent(intent: Intent) {

    if (Intent.ACTION_SEARCH == intent.action) {
        val query = intent.getStringExtra(SearchManager.QUERY)
        val c = db.getWordMatches(query, null)
        // process Cursor and display results
    }
}

Java

DatabaseTable db = new DatabaseTable(this);

...

private void handleIntent(Intent intent) {

    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
        String query = intent.getStringExtra(SearchManager.QUERY);
        Cursor c = db.getWordMatches(query, null);
        // process Cursor and display results
    }
}