《第一行代碼》第三版之數據存儲方案(八)

       本章我們介紹了數據存儲方案:文件存儲、SharedPreference和SQLite。文件存儲的讀寫,SharedPreference的讀寫和實現記住密碼功能;SQLite的創建升級數據庫以及CRUD;SQLite數據庫的最佳實踐:事務以及升級數據庫;最後我們講了利用高階函數和KTX庫簡化SharedPreference和ContentValues的寫法。
7.1.持久化技術簡介
       保存於內存的數據是瞬時狀態,瞬時數據無法持久化保存。保存在存儲設備的數據是持久狀態的,有3種方式可以實現:文件存儲、SharedPreference存儲和數據庫存儲。
7.2.文件存儲
      作用數據原封不動的保存到文件中,適用於存儲簡單地文本數據或二進制數據。
      新建項目FilrPersistenceTest項目,使用Context類的openFileoutput方法可以將數據存儲至指定文件夾,openFileInput讀取指定的文件。MainActivity中的onDestroy方法中調用save方法,onCreate方法中調用load方法,代碼示例如下:

package com.example.myapplication

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*
import java.lang.StringBuilder

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inputtext = load()
        //如果讀到內容不爲空,setText填充後使用setSelection將光標移動到末尾位置
        if (inputtext.isNotEmpty()) {
            edit_text.setText(inputtext)
            edit_text.setSelection(inputtext.length)
            Toast.makeText(this, "Restore succeed", Toast.LENGTH_SHORT).show()
        }
    }

    //銷燬之前調用了save方法用於存儲文本框內的數據
    override fun onDestroy() {
        super.onDestroy()
        val inputText = edit_text.text.toString()
        save(inputText)
    }

    fun save(inputText: String) {
        try {
            //Context提供openFileOutput方法,將數據存儲到指定文件夾,第一個參數是文件名,默認存儲至data/data/packagename/file目錄下
            // 第二個參數是文件的操作模式,MODE_PRIVATE和MODE——APPEND,前者表示相同文件名,寫入內容覆蓋之前內容;後者該文件已存在,追加內容,不存在創建文件
            //第一步:openFileOutput構建FileOutputStream對象
            val output = openFileOutput("data", Context.MODE_PRIVATE)
            //第二步:構建一個OutputStreamWriter對象後構造一個BufferWriter對象
            val writer = BufferedWriter(OutputStreamWriter(output))
            //第三步:通過bufferwriter開始將文本內容寫入文件當中
            //Kotlin內置擴展函數use,他保證了代碼執行完成之後自動將外層流關閉,無需finally方法手動關閉流
            writer.use {
                it.write(inputText)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }


    fun load(): String {
        val content = StringBuilder()
        try {
            //第一步:openFileInput獲取FileInputStream對象
            val input = openFileInput("data")
            //第二步:藉助它構建InputStreamReader後構建BufferedReader對象
            val reader = BufferedReader(InputStreamReader(input))
            //第三步:forEachLine內置擴展函數,每行內容都會調到Lambda表達式並拼接
            reader.use {
                reader.forEachLine {
                    content.append(it)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return content.toString()
    }
}

      相應的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
<!--界面上有一個文本框-->
    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type sth" />
</LinearLayout>

7.3.SharedPreference存儲
      文件存儲不適合存儲複雜結構型數據,SharedPreference使用鍵值對來存儲數據,讀取數據時根據鍵將值讀取出來,支持多種不同數據類型存儲。
7.3.1.將數據存儲到SharedPreference當中
      主要通過Context的getSharedPreference方法獲取SharedPreference對象,調用edit()後獲取對象,putString等方法存儲鍵值對,最後使用apply方法進行提交。
7.3.2.從SharedPreference中讀取數據
       很簡單,要通過Context的getSharedPreference方法獲取SharedPreference對象,調用getString等方法根據鍵獲取相應的值。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        save_button.setOnClickListener {
            //第一步:獲取SharedPreference對象,有兩種方式:context類的getSharedPreferences和Activity類的getPreference方法,前者可以指定文件名,後者將Activity類名作爲文件名
            //第二步:調用SharedPreference對象的editor方法獲取相應的對象
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            //第三步:Editor對象添加數據,添加布爾類型數據、String類型等數據
            editor.putString("name", "Tom")
            editor.putInt("age", 20)
            editor.putBoolean("married", false)
            //第四步:apply方法將數據進行提交,完成數據庫存儲操作
            editor.apply()
        }
        restore_button.setOnClickListener{
            val prefs = getSharedPreferences("data",Context.MODE_PRIVATE)
            //兩個參數:第一個是鍵,根據鍵得到相應的值;第二個是默認值,如果鍵找不到對應的值回憶默認值返回。
            val name = prefs.getString("name","")
            val age = prefs.getInt("age",0)
            val married = prefs.getBoolean("married",false)
            Log.d("MainActivity","name is $name")
            Log.d("MainActivity","age is $age")
            Log.d("MainActivity","married is $married")
        }
    }
}

      相對應的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <!--界面上有兩個點擊鍵-->
    <Button
        android:id="@+id/save_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data" />
    <Button
        android:id="@+id/restore_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Restore Data" />
</LinearLayout>

7.3.3.基於SharedPreference實現記住密碼的功能
     打開BroadcastBestPractice項目,修改佈局文件Activity_login.xml項目,複選框控件來表示用戶是否需要記住密碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <!--功能:登錄界面的設計-->
     ....
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <CheckBox
            android:id="@+id/rememberPass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Remember Text"/>
    </LinearLayout>
    ....
</LinearLayout>

     修改LoginActivity,利用SharedPreference實現記住密碼的功能:

/**
 * 功能:非常簡單的登錄功能,若成功,跳轉至MainActivity,否則提示賬號或者密碼錯誤
 */
class LoginActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        val prefs = getPreferences(Context.MODE_PRIVATE)
        val isRemember = prefs.getBoolean("remember_password", false)
        if (isRemember) {
            //將賬號和密碼都輸入到文本框中
            val account = prefs.getString("account", "")
            val password = prefs.getString("password", "")
            account_edit.setText(account)
            password_edit.setText(password)
            rememberPass.isChecked = true
        }
        login.setOnClickListener {
            val account = account_edit.text.toString()
            val password = password_edit.text.toString()
            if (account == "admin" && password == "123") {
                val editor = prefs.edit()
                if (rememberPass.isChecked) {//檢查複選框是否被選中
                    editor.putBoolean("remember_password", true)
                    editor.putString("account", account)
                    editor.putString("password", password)
                } else {
                    editor.clear()
                }
                editor.apply()
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
                finish()
            } else {
                Toast.makeText(this, "account or pass is invalid", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

7.4.SQLite數據庫存儲
     SQLite是輕量級關係型數據庫,SharedPreference適用於存儲簡單數據和鍵值對,數據量大、結構性複雜的數據應當考慮使用SQLite數據庫。
7.4.1.創建數據庫
       SQLiteOpenHelper是一個抽象類,用於簡單對數據庫進行創建和升級。創建DatabaseTest項目。新建MyDatabaseHelper類。

package com.example.myapplication

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

//SQLiteHelper是一個抽象類,構造方法接收四個參數:context、數據庫名、返回自定義cursor一般爲null、數據庫版本號
//創建數據庫文件一般會放在data/data/packagename/databases/目錄下,一般需要複寫onCreate和onUpGrade方法
class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    //創建Book表,integer是正整型,real是浮點型,text是文本類型, blob是二進制類型。
    //primary key將id列爲主鍵,autoincrement表示id列是自增長的。
   private val createbooks = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "prices real," +
            "pages integer," +
            "name text)"
    override fun onCreate(db: SQLiteDatabase?) {
        //執行建表語句
        db?.execSQL(createbooks)
        Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldversoin: Int, newversion: Int) {
        TODO("Not yet implemented")
    }
}

       其次,修改佈局文件爲一個按鈕:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

   <Button
       android:id="@+id/create_db"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Create Database"/>

</LinearLayout>

       最後修改MainActivity的方法,調用創建數據庫的getWritablebase方法。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //構造一個SQLiteOpenHelper對象,通過構造函數將數據庫指定爲book.db,版本號爲1.
        val dbHelper = MyDatabaseHelper(this,"BookStore.db",1)
        create_db.setOnClickListener{
            //點擊後調用getWritableDatabase或者getReadableDatabase創建或打開一個現有的數據庫,若存在直接打開,否則創建新的。
            //並返回一個可讀對象。
            //若滿時,前者出現異常,後者返回可讀對象。
            dbHelper.writableDatabase
        }
    }
}

7.4.2.升級數據庫
      MyDatabaseHelper類複寫的onUpgrade方法用於對數據庫進行升級,已經有Book表,再添加category表用於記錄圖書分類。
      三步走:1.建立SQL語句以創建category表,在onCreate方法中執行;2.在onUpgrade方法中添加刪表語句以及調用onCreate方法;3.爲使onUpgrade方法執行,SQLiteOpenHelper的數據庫版本號要比之前的大。

//SQLiteHelper是一個抽象類,構造方法接收四個參數:context、數據庫名、返回自定義cursor一般爲null、數據庫版本號
//創建數據庫文件一般會放在data/data/packagename/databases/目錄下,一般需要複寫onCreate和onUpGrade方法
class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    //創建Book表,integer是正整型,real是浮點型,text是文本類型, blob是二進制類型。
    //primary key將id列爲主鍵,autoincrement表示id列是自增長的。
    private val createbooks = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "prices real," +
            "pages integer," +
            "name text)"

    ///創建記錄圖書分類的Castegory的SQL語句
    private val createcategory = "create table Category(" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase?) {
        //執行建表語句
        db?.execSQL(createbooks)
        db?.execSQL(createcategory)
        Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
    }

    //數據庫中已經存在Book表或者Category表,就將這兩條表刪除,重新執行onCreate方法創建
    // 如果發現創建時已經存在此表,那麼會直接報錯、
    override fun onUpgrade(db: SQLiteDatabase, oldversoin: Int, newversion: Int) {
        db.execSQL("drop table if exists Book")
        db.execSQL("drop table if exists Category")
        onCreate(db)
    }
}
     //只要傳入比1大的數,就可以讓onUpgrade方法得到執行
        val dbHelper = MyDatabaseHelper(this,"BookStore.db",2)

 7.4.3.CRUD
      CRUD分別指添加insert(create)、查詢select(retrive)、更新update(update)和刪除delete(delete)。前面提到getWritableDatabase或者getReadableDatabase返回一個SQLiteDataBase對象,藉助此可以進行CRUD操作。

                                        
      佈局文件activity_main.xml。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

   <Button
       android:id="@+id/create_db"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Create Database"/>
   <Button
       android:id="@+id/add_data"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Add Data"/>

   <Button
       android:id="@+id/update_data"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Update Data"/>

   <Button
       android:id="@+id/delete_data"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Delete Data"/>

   <Button
       android:id="@+id/query_data"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Query Data"/>
</LinearLayout>

      修改MainActivity.java增加CRUD的操作:可以使用輔助性方法來完成操作數據庫的操作,也可以使用SQL來直接操作數據庫。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //構造一個SQLiteOpenHelper對象,通過構造函數將數據庫指定爲book.db,版本號爲1.
        //只要傳入比1大的數,就可以讓onUpgrade方法得到執行
        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
        //實現創建數據庫的功能
        create_db.setOnClickListener {
            //點擊後調用getWritableDatabase或者getReadableDatabase創建或打開一個現有的數據庫,若存在直接打開,否則創建新的。
            //並返回一個可讀對象。
            //若滿時,前者出現異常,後者返回可讀對象。
            dbHelper.writableDatabase
        }
        //實現添加數據的功能
        add_data.setOnClickListener {
            //獲取並返回DataBase對象
            val db = dbHelper.writableDatabase
            val values1 = ContentValues().apply {
                put("name", "The Da Vinci Code")
                put("author", "Dan Brown")
                put("pages", 454)
                put("prices", 16.96)
            }
            //insert方法用於向表中添加數據
            //第一個參數爲表名,往那張表中添加;第二個參數用於指定哪列爲null,一般不使用;
            // 第三個參數是ContentValue對象,提供一系列put方法添加數據。
            db.insert("Book", null, values1)
            val values2 = ContentValues().apply {
                put("name", "The Lost Symbol")
                put("author", "Dan Brown")
                put("pages", 510)
                put("prices", 19.95)
            }
            db.insert("Book", null, values2)
            //直接使用SQL來操作數據庫,無需Android的SQLIte輔助性語言
            db.execSQL(
                "insert into Book(name ,author,pages,prices) values(?,?,?,?)",
                arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
            )
        }
        //實現更新數據的功能,將The Da Vinci Code書的價格調低一些
        update_data.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values = ContentValues()
            values.put("prices", 10.99)
            //update共接收四個參數,第一個是那個表,第二個是要更新的ContentValues數據;
            // 第三、第四個參數是更新某一行或者某幾行的數據,不指定的話會默認更新所有行
            //第三個對應的是SQL語句的where部分,表示更新所有name=?的部分,?是佔位符,第四個參數爲其提供內容
            //arrayof是Kotlin提供的用於便捷創建數據的內置方法
            db.update("Book", values, "name=?", arrayOf("The Da Vinci Code"))
            db.execSQL(
                "update Book set prices =? where name = ?",
                arrayOf("10.99", "The Da Vinci Code")
            )
        }
        //刪除Book表中超過500頁的書
        delete_data.setOnClickListener {
            val db = dbHelper.writableDatabase
            //delete方法第一個參數是表明,第二、三個是用於約束刪除某一行或幾行的參數,不指定的話會刪除所有
            db.delete("Book", "pages>?", arrayOf("500"))
            db.execSQL("delete from Book where pages >?", arrayOf("500"))

        }
        //查詢Book表中的所有數據,並將其Log出來
        query_data.setOnClickListener {
            val db = dbHelper.writableDatabase
            //query共七個參數,調用後可以返回一個cursor對象
            val cursor = db.query("Book", null, null, null, null, null, null)
            //將指針移動到第一行的位置後進入循環
            if (cursor.moveToFirst()) {
                //便利查詢到的每一行數據,通過Cursor的getColumnIndex去獲取某一列在表中對應位置的索引,然後通過索引來獲取數值。最後Log出來。
                do {
                    //遍歷cursor對象
                    val name = cursor.getString(cursor.getColumnIndex("name"))
                    val author = cursor.getString(cursor.getColumnIndex("author"))
                    val pages = cursor.getString(cursor.getColumnIndex("pages"))
                    val prices = cursor.getString(cursor.getColumnIndex("prices"))
                    Log.d("MainActivity", "book name is $name")
                    Log.d("MainActivity", "book author is $author")
                    Log.d("MainActivity", "book pages is $pages")
                    Log.d("MainActivity", "book prices is $prices")
                } while (cursor.moveToNext())
            }
            cursor.close()

            val cursor1 = db.rawQuery("select * from Book", null)
            cursor1.close()
        }
    }
}

7.5.SQLite數據庫的最佳實踐
7.5.1.使用事務

        SQLite支持事務,事務特性是ACID:原子性(Atomicity)是操作這些指令時,要麼全部執行成功,要麼全部不執行;一致性(Consistency)是事務的執行使數據從一個狀態轉換爲另一個狀態,但是對於整個數據的完整性保持穩定;隔離性(Isolation)是當多個用戶併發訪問數據庫時,比如操作同一張表時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。持久性(Durability)是當事務正確完成後,它對於數據的改變是永久性的。
       在DatabaseTest項目基礎上修改,譬如Book表全部廢除替換成新數據,delete之後insert,要保證刪除舊數據和添加新數據的操作必須一起完成,否則繼續保留原來的舊數據。

        replace_data.setOnClickListener {
            val db = dbHelper.writableDatabase
            db.beginTransaction()//beginTransaction開啓事務
            try {
                db.delete("Book", null, null)
                //手動拋出異常,讓事務失敗,代碼回滾,事務的原子性。註釋掉可以成功刪除該表
//                if (true) {
//                    //手動拋出異常,讓事務失敗
//                    throw NullPointerException()
//                }
                val values = ContentValues().apply {
                    put("name", "Game of Thrones")
                    put("author", "George Martin")
                    put("pages", 720)
                    put("prices", 20.85)
                }
                db.insert("Book", null, values)
                //表示事務已經執行成功了
                db.setTransactionSuccessful()
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                //結束事務
                db.endTransaction()
            }
        }

7.5.2.升級數據庫的最佳寫法
       之前onupgrade有點粗暴,有沒有更好地數據庫升級方式?答案是有的。第二版是在只有Book表的基礎上再添加Category表;第三版是給Book表添加category_id列。充分考慮到跨版本升級與新版本安裝時的情況,代碼示例如下:

package com.example.myapplication

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

//SQLiteHelper是一個抽象類,構造方法接收四個參數:context、數據庫名、返回自定義cursor一般爲null、數據庫版本號
//創建數據庫文件一般會放在data/data/packagename/databases/目錄下,一般需要複寫onCreate和onUpGrade方法
class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    //創建Book表,integer是正整型,real是浮點型,text是文本類型, blob是二進制類型。
    //primary key將id列爲主鍵,autoincrement表示id列是自增長的。
    private val createbooks = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "prices real," +
            "pages integer," +
            "name text," +
            "category_id integer)"

    ///創建記錄圖書分類的Castegory的SQL語句
    private val createcategory = "create table Category(" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase?) {
        //執行建表語句
        db?.execSQL(createbooks)
        db?.execSQL(createcategory)
        Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
    }

    //數據庫中已經存在Book表或者Category表,就將這兩條表刪除,重新執行onCreate方法創建
    // 如果發現創建時已經存在此表,那麼會直接報錯、
    override fun onUpgrade(db: SQLiteDatabase, oldversoin: Int, newversion: Int) {
//        db.execSQL("drop table if exists Book")
//        db.execSQL("drop table if exists Category")
//        onCreate(db)
        //第二版加入了添加category表的功能,無論版本是1、2都可以執行到。要充分考慮到第一版升到第二版或直接升到第三版的情況。
        if (oldversoin <= 1) {
            db.execSQL(createcategory)
        }
        //第三版加入了category_id列,無論版本號是1、2、3,都可以照顧到
        if (oldversoin <= 2) {
            db.execSQL("alter table Book add column category_id integer")
        }
    }
}

7.6.Kotlin課堂:高階函數的應用
7.6.1.簡化SharedPreference的用法

       利用高階函數簡化SharedPreference的用法,之前的代碼如下:

          //第1步:調用SharedPreference對象的editor方法獲取相應的對象
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            //第2步:Editor對象添加數據,添加布爾類型數據、String類型等數據
            editor.putString("name", "Tom")
            editor.putInt("age", 20)
            editor.putBoolean("married", false)
            //第3步:apply方法將數據進行提交,完成數據庫存儲操作
            editor.apply()

       創建SharedPreference.kt文件:

package com.example.myapplication

import android.content.SharedPreferences
//SharedPreferences類中添加一個open函數,接收函數類型的參數。open函數內擁有SharedPre的上下文
fun SharedPreferences.open(block:SharedPreferences.Editor.() -> Unit){
    //直接掉用edit來獲取SharedPreference.Editor對象。
    val editor = edit()
    //open函數接受的是SharedPreferences.Editor的函數類型參數,調用editor.block()對函數類型參數進行調用
    editor.block()
    //最後提交數據
    editor.apply()
}
接着使用SharedPreference調用高階函數存儲數據:
            //直接在SharedPreference對象上調用open函數,接着在Lambda表達式中完成數據的添加操作
            getSharedPreferences("data", Context.MODE_PRIVATE).open {
                //此時擁有了SharedPreference.Editor的上下文環境,只需要調用相應的put方法即可。
                putString("name", "Tom")
                putInt("age", 28)
                putBoolean("married", false)
            }

7.6.2.簡化ContentValues的用法
       ContentValues的普通寫法:

            val values2 = ContentValues()
            values2.put("name", "The Lost Symbol")
            values2.put("author", "Dan Brown")
            values2.put("pages", 510)
            values2.put("prices", 19.95)
            db.insert("Book", null, values2)
  //ContentValues寫法利用apply簡化:
            val values2 = ContentValues().apply {
                put("name", "The Lost Symbol")
                put("author", "Dan Brown")
                put("pages", 510)
                put("prices", 19.95)
            }

       這樣還不夠,利用mapOf函數的寫法,使用”Apple” to 1語法結構快速創建一個鍵值對。在Kotlin中可以使用A to B的語法結構創建一個Pair對象。創建ContentValues.kt文件。

package com.example.myapplication

import android.content.ContentValues

//cvOf及接受一個Pair參數,在參數前面使用了vararg關鍵字,即允許0個、1個或者多個Pair類型的參數。這些參數會被傳入然後通過for-in循環
//fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
//    //Pair類型實踐支隊,ContentValues的鍵是String類型,將Pair鍵的泛型指定爲String,值可以爲任意類型,Any?允許傳入空值、字符串類等等類型
//    val cv = ContentValues()
//    //遍歷Pairs參數列表,取出其中的值放在ContentValues中。利用when判斷並覆蓋ContentValues支持的所有類型
//    for (pair in pairs) {
//        val key = pair.first
//        val value = pair.second
//        when (value) {
//            is Int -> cv.put(key, value)
//            is Long -> cv.put(key, value)
//            is Short -> cv.put(key, value)
//            is Float -> cv.put(key, value)
//            is Double -> cv.put(key, value)
//            is Boolean -> cv.put(key, value)
//            is String -> cv.put(key, value)
//            is Byte -> cv.put(key, value)
//            is ByteArray -> cv.put(key, value)
//            null -> cv.putNull(key)
//        }
//    }
//    return cv
//}
//利用高階函數再行簡化
fun cvOf(vararg pairs: Pair<String, Any?>) = ContentValues().apply {
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when (value) {
            is Int -> put(key, value)
            is Long -> put(key, value)
            is Short -> put(key, value)
            is Float -> put(key, value)
            is Double -> put(key, value)
            is Boolean -> put(key, value)
            is String -> put(key, value)
            is Byte -> put(key, value)
            is ByteArray -> put(key, value)
            null -> putNull(key)
        }
    }
}

       調用的函數如下:

   val values2 = cvOf("name" to "The Lost Symbol","author" to "Dan Brown", "pages" to 510,"prices" to 19.95)
   db.insert("Book", null, values2)

       另外,更簡化的代碼如下,我們在build.gradle中導入KTX包,然後簡單調用即可:

  implementation 'androidx.core:core-ktx:1.1.0'
            val values2 = contentValuesOf(
                "name" to "The Lost Symbol",
                "author" to "Dan Brown",
                "pages" to 510,
                "prices" to 19.95
            )
            db.insert("Book", null, values2)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章