ذخیره سازی و جستجوی داده ها

روش Compose را امتحان کنید
Jetpack Compose جعبه ابزار UI توصیه شده برای اندروید است. با نحوه افزودن قابلیت جستجو در Compose آشنا شوید.

راه های زیادی برای ذخیره داده های شما وجود دارد، مانند یک پایگاه داده آنلاین، در پایگاه داده محلی SQLite یا حتی در یک فایل متنی. این شما هستید که تصمیم می گیرید بهترین راه حل برای برنامه شما چیست. این درس به شما نشان می دهد که چگونه یک جدول مجازی SQLite بسازید که بتواند جستجوی کامل متن قوی را ارائه دهد. جدول با داده های یک فایل متنی پر شده است که شامل یک جفت کلمه و تعریف در هر خط در فایل است.

جدول مجازی را ایجاد کنید

یک جدول مجازی مانند جدول SQLite رفتار می کند، اما به جای فایل پایگاه داده، یک شی در حافظه را از طریق callback می خواند و می نویسد. برای ایجاد یک جدول مجازی، یک کلاس برای جدول ایجاد کنید:

کاتلین

class DatabaseTable(context: Context) {

    private val databaseOpenHelper = DatabaseOpenHelper(context)

}

جاوا

public class DatabaseTable {
    private final DatabaseOpenHelper databaseOpenHelper;

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

یک کلاس داخلی در DatabaseTable ایجاد کنید که SQLiteOpenHelper گسترش دهد. کلاس SQLiteOpenHelper متدهای انتزاعی را تعریف می کند که باید آنها را نادیده بگیرید تا جدول پایگاه داده شما ایجاد و در صورت لزوم ارتقا یابد. به عنوان مثال، در اینجا کدی وجود دارد که یک جدول پایگاه داده را نشان می دهد که حاوی کلماتی برای یک برنامه فرهنگ لغت است:

کاتلین

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)
        }

    }
}

جاوا

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);
        }
    }
}

جدول مجازی را پر کنید

اکنون جدول برای ذخیره به داده نیاز دارد. کد زیر به شما نشان می دهد که چگونه یک فایل متنی (واقع در res/raw/definitions.txt ) را بخوانید که حاوی کلمات و تعاریف آنها است، چگونه آن فایل را تجزیه کنید، و چگونه هر خط از آن فایل را به عنوان یک ردیف در جدول مجازی درج کنید. این همه در یک رشته دیگر انجام می شود تا از قفل شدن رابط کاربری جلوگیری شود. کد زیر را به کلاس داخلی DatabaseOpenHelper خود اضافه کنید.

نکته: همچنین ممکن است بخواهید یک تماس برگشتی تنظیم کنید تا فعالیت رابط کاربری خود را از تکمیل این رشته مطلع کنید.

کاتلین

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)
}

جاوا

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);
}

روش loadDictionary() را هر جا که مناسب باشد برای پر کردن جدول فراخوانی کنید. یک مکان خوب در متد onCreate() از کلاس DatabaseOpenHelper ، درست پس از ایجاد جدول است:

کاتلین

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

جاوا

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

هنگامی که جدول مجازی را ایجاد و پر کردید، از درخواست ارائه شده توسط SearchView خود برای جستجوی داده ها استفاده کنید. متدهای زیر را به کلاس DatabaseTable اضافه کنید تا یک عبارت SQL بسازید که پرس و جو را جستجو می کند:

کاتلین

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
}

جاوا

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;
}

با فراخوانی getWordMatches() یک پرس و جو را جستجو کنید. هر نتیجه منطبقی در یک Cursor برگردانده می‌شود که می‌توانید آن را تکرار کنید یا از آن برای ساخت ListView استفاده کنید. این مثال getWordMatches() در متد handleIntent() فعالیت قابل جستجو فراخوانی می کند. به خاطر داشته باشید که فعالیت قابل جستجو به دلیل فیلتر قصدی که قبلاً ایجاد کرده‌اید، پرس و جوی داخل intent ACTION_SEARCH را به عنوان یک اضافی دریافت می‌کند:

کاتلین

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
    }
}

جاوا

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
    }
}