Kotlin for Android (五)

创建SQLiteHelper

一般使用SqliteOpenHelper 去调用 getReadableDatabase() 或者 getWritableDatabase() ,然后我们可以执行我们的搜索并拿到结果。在这之后,我们不能忘记调用 close()

ManagedSqliteOpenHelper
  • 函数中,最后一行表示返回值。因为T没有任何的限制,所以我们可以返回任何对象。甚至如果我们不想返回任何值就使用 Unit,try-finally保证关闭数据库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    val result = forecastDbHelper.use {
    val queriedObject = ...
    queriedObject
    }

    fun <T> use(f: SQLiteDatabase.() -> T): T {
    try {
    return openDatabase().f()
    } finally {
    closeDatabase()
    }
    }
定义表
  • CityForecastTable 提供了表的名字还有需要列:一个id(这个城市的zipCode),城市的名称和所在国家,DayForecast天气信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    object CityForecastTable {
    val NAME = "CityForecast"
    val ID = "_id"
    val CITY = "city"
    val COUNTRY = "country"
    }

    object DayForecastTable {
    val NAME = "DayForecast"
    val ID = "_id"
    val DATE = "date"
    val DESCRIPTION = "description"
    val HIGH = "high"
    val LOW = "low"
    val ICON_URL = "iconUrl"
    val CITY_ID = "cityId"
    }
实现SqliteOpenHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun SQLiteDatabase.createTable(tableName: String, ifNotExists: Boolean = false, vararg columns: Pair<String, SqlType>) {
val escapedTableName = tableName.replace("`", "``")
val ifNotExistsText = if (ifNotExists) "IF NOT EXISTS" else ""
execSQL(
columns.map { col ->
"${col.first} ${col.second.render()}"
}.joinToString(", ", prefix = "CREATE TABLE $ifNotExistsText `$escapedTableName`(", postfix = ");")
)
}

db.createTable(CityForecastTable.NAME, true,
Pair(CityForecastTable.ID, INTEGER + PRIMARY_KEY),
Pair(CityForecastTable.CITY, TEXT),
Pair(CityForecastTable.COUNTRY, TEXT))

第一个参数是表的名称
第二个参数,当是true的时候,创建之前会检查这个表是否存在。
第三个参数是一个 Pair 类型的 vararg 参数。 vararg 也存在在Java中,
这是一种在一个函数中传入联系很多相同类型的参数。这个函数也接收一个对
象数组。
Anko中有一种叫做 SqlType 的特殊类型,它可以与 SqlTypeModifiers 混合,
比如 PRIMARY_KEY 。 + 操作符像之前那样被重写了。这个 plus 函数会把两者
通过合适的方式结合起来,然后返回一个新的 SqlType

1
2
3
4
fun SqlType.plus(m: SqlTypeModifier) : SqlType {
return SqlTypeImpl(name, if (modifier == null) m.toString()
else "$modifier $m")
}

  • public fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)因为带有一个函数参数的函数可以被用于inline,所以结果非常清晰:val pair = object1 to object2
  • 简化后的代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class ForecastDbHelper(ctx: Context = App.instance) : ManagedSQLiteOpenHelper(App.instance,
    ForecastDbHelper.DB_NAME, null, ForecastDbHelper.DB_VERSION) {
    override fun onCreate(db: SQLiteDatabase) {
    db.createTable(CityForecastTable.NAME, true,
    CityForecastTable.ID to INTEGER + PRIMARY_KEY,
    CityForecastTable.CITY to TEXT,
    CityForecastTable.COUNTRY to TEXT)

    db.createTable(DayForecastTable.NAME, true,
    DayForecastTable.ID to INTEGER + PRIMARY_KEY,
    DayForecastTable.DATE to INTEGER,
    DayForecastTable.DESCRIPTION to TEXT,
    DayForecastTable.HIGH to INTEGER,
    DayForecastTable.LOW to INTEGER,
    DayForecastTable.ICON_URL to TEXT,
    DayForecastTable.CITY_ID to INTEGER)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    db.dropTable(CityForecastTable.NAME, true)
    db.dropTable(DayForecastTable.NAME, true)
    onCreate(db)
    }

    companion object {
    val DB_NAME = "forecast.db"
    val DB_VERSION = 1
    val instance: ForecastDbHelper by lazy {
    ForecastDbHelper()
    }
    }
    }

    instance 这个属性使用了 lazy 委托,它表示直到它真的被调用才会被创建。
    如果数据库没有被使用,就没有去创建这个对象。一般 lazy 委托的代码块可以阻止在多个不同的线程中创建多个对象。这个只会发生在两个线程在同时访问这个 instance 对象, lazy 委托是线程安全的

集合和函数操作符

  • 本地接口

    Iterable:父类。所有我们可以遍历一系列的都是实现这个接口
    MutableIterable:一个支持遍历的同时可以执行删除的Iterables
    Collection:这个类相是一个范性集合。我们通过函数访问可以返回集合的size、是否为空、是否包含一个或者一些item。这个集合的所有方法提供查询,因为collection是不可修改的
    MutableCollection:一个支持增加和删除item的Collection。它提供了额外的函数,比如 add 、 remove 、 clear 等等
    List:可能是最流行的集合类型。它是一个范性有序的集合。因为它的有序,我们可以使用 get 函数通过position来访问
    MutableList:一个支持增加和删除item的List
    Set:一个无序并不支持重复item的集合
    MutableSet:一个支持增加和删除item的Set
    Map:一个key-value对的collection。key在map中是唯一的,也就是说不能有两对key是一样的键值对存在于一个map中
    MutableMap:一个支持增加和删除item的map

    总数操作符
  • any 如果至少有一个元素符合给出的判断条件,则返回true

    1
    2
    3
    val list = listOf(1, 2, 3, 4, 5, 6)
    assertTrue(list.any { it % 2 == 0 }) true
    assertFalse(list.any { it > 10 }) false
  • all 如果全部的元素符合给出的判断条件,则返回true

    1
    2
    assertTrue(list.all { it < 10 })  true
    assertFalse(list.all { it % 2 == 0 }) false
  • count 返回符合给出判断条件的元素总数

    1
    assertEquals(3, list.count { it % 2 == 0 }) true
  • fold 在一个初始值的基础上从第一项到最后一项通过一个函数累计所有的元素,所有元素相加,并加上初始值

    1
    assertEquals(25, list.fold(4) { total, next -> total + next }) true
  • foldRight 与 fold 一样,但是顺序是从最后一项到第一项

  • foreEach 遍历所有元素,并执行给定的操作

    1
    list.forEach { println(it) }
  • forEachIndexed 与 forEach ,但是我们同时可以得到元素的index

    1
    list.forEachIndexed { index, i -> println("position $index value $i") }
  • max/min 返回最大值/最小值,没有返回null

  • maxBy/minBy 根据给定的函数返回最大/最小的一项,如果没有则返回null

    1
    2
    // The element whose negative is greater
    assertEquals(1, list.maxBy { -it })
  • none 如果没有任何元素与给定的函数匹配,则返回true

    1
    2
    // No elements are divisible by 7
    assertTrue(list.none { it % 7 == 0 }) true
  • reduce与 fold 一样,但是没有一个初始值。通过一个函数从第一项到最后一项进行累

    1
    assertEquals(21, list.reduce { total, next -> total + next })
  • reduceRight与 reduce 一样,但是顺序是从最后一项到第一项

    1
    2
    assertEquals(21, list.reduceRight { total, next -> total + next
    })
  • sumBy返回所有每一项通过函数转换之后的数据的总和

    1
    assertEquals(3, list.sumBy { it % 2 })
过滤操作符
  • drop返回包含去掉前n个元素的所有元素的列表

    1
    assertEquals(listOf(5, 6), list.drop(4))
  • dropWhile返回根据给定函数从第一项开始去掉指定元素的列表

    1
    assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 })
  • dropLastWhile返回根据给定函数从最后一项开始去掉指定元素的列表

    1
    assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 })
  • filter过滤所有符合给定函数条件的元素

    1
    assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 })
  • 1
    2
    3
    4
    val listWithNull = listOf(1, 2, null)
    Log.e(tag, "filterNotNull " + listWithNull.filterNotNull())

    filterNotNull [1, 2]
  • slice 过滤一个list中指定index的元素

    1
    2
    3
    Log.e(tag, "slice " + list.slice(listOf(1, 2, 4)))

    slice [2, 3, 5]
  • take返回从第一个开始的n个元素, takeLast返回从最后一个开始的n个元素,takeWhile返回从第一个开始符合给定函数条件的元素

    1
    2
    3
    assertEquals(listOf(1, 2), list.take(2))
    assertEquals(listOf(5, 6), list.takeLast(2))
    assertEquals(listOf(1, 2), list.takeWhile { it < 3 })
  • flatMap遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中

    1
    2
    assertEquals(listOf(1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7),
    list.flatMap { listOf(it, it + 1) })
  • groupBy 返回一个根据给定函数分组后的map

    1
    2
    assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2,
    4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" })
  • map返回一个每一个元素根据给定的函数转换所组成的List

    1
    assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 })
  • mapIndexed返回一个每一个元素根据给定的包含元素index的函数转换所组成的List

    1
    2
    assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { ind
    ex, it -> index * it })
  • mapNotNull返回一个每一个非null元素根据给定的函数转换所组成的List

    1
    2
    assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2
    })
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
            val list = listOf(1, 1, 3, 4, 5, 6)
    Log.e(tag, "any1 " + list.any { it % 2 == 0 })
    Log.e(tag, "any2 " + list.any { it > 10 })
    Log.e(tag, "all1 " + list.all { it < 10 })
    Log.e(tag, "all2 " + list.all { it % 2 == 0 })
    Log.e(tag, "count " + list.count { it % 2 == 0 })
    Log.e(tag, "fold " + list.fold(4) { total, next -> total + next })
    Log.e(tag, "foldRight " + list.foldRight(4) { total, next -> total + next })
    list.forEach { println(it) }
    list.forEachIndexed { index, i -> println("position $index value $i") }
    Log.e(tag, "max " + list.max())
    Log.e(tag, "maxBy " + list.maxBy { -it })
    Log.e(tag, "min " + list.min())
    Log.e(tag, "minBy " + list.minBy { -it })
    Log.e(tag, "none " + list.none { it % 7 == 0 })
    Log.e(tag, "reduce " + list.reduce { total, next -> total + next })
    Log.e(tag, "reduce right " + list.reduceRight { total, next -> total + next })
    Log.e(tag, "sumBy " + list.sumBy { it % 2 })
    Log.e(tag, "drop " + list.drop(4))
    Log.e(tag, "dropWhile " + list.dropWhile { it > 2 })
    Log.e(tag, "dropLastWhile " + list.dropLastWhile { it > 2 })
    Log.e(tag, "filter " + list.filter { it > 2 })
    Log.e(tag, "filterNot " + list.filterNot { it > 2 })
    val listWithNull = listOf(1, 2, null)
    Log.e(tag, "filterNotNull " + listWithNull.filterNotNull())
    Log.e(tag, "slice " + list.slice(listOf(1, 2, 4)))
    Log.e(tag, "flatMap " + list.flatMap { listOf(it, it + 1) })
    Log.e(tag, "groupBy " + list.groupBy { if (it % 2 == 0) "even" else "odd" })
    Log.e(tag, "map " + list.map { it * 2 })
    Log.e(tag, "partition " + list.partition { it % 2 == 0 })
    Log.e(tag, "plus " + list + listOf(7, 8))
    Log.e(tag, "zip " + list.zip(listOf(7, 4, 9, 8)))
    Log.e(tag, "unzip " + listOf(Pair(5, 7), Pair(6, 8)).unzip())

    E/MainActivity: any1 true
    any2 false
    all1 true
    all2 false
    count 2
    fold 24
    foldRight 24
    I/System.out: 1
    1
    3
    4
    5
    6
    position 0 value 1
    position 1 value 1
    position 2 value 3
    position 3 value 4
    position 4 value 5
    position 5 value 6
    E/MainActivity: max 6
    maxBy 1
    min 1
    E/MainActivity: minBy 6
    none true
    reduce 20
    reduce right 20
    sumBy 4
    drop [5, 6]
    dropWhile [1, 1, 3, 4, 5, 6]
    dropLastWhile [1, 1]
    filter [3, 4, 5, 6]
    filterNot [1, 1]
    filterNotNull [1, 2]
    slice [1, 3, 5]
    flatMap [1, 2, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7]
    groupBy {odd=[1, 1, 3, 5], even=[4, 6]}
    map [2, 2, 6, 8, 10, 12]
    partition ([4, 6], [1, 1, 3, 5])
    plus [1, 1, 3, 4, 5, 6][7, 8]
    zip [(1, 7), (1, 4), (3, 9), (4, 8)]
    unzip ([5, 6], [7, 8])
元素操作符
生产操作符
顺序操作符

创建数据库model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* SQLite表与对象之间的互相映射
*/
class CityForecast(val map: MutableMap<String, Any?>,
val dailyForecast: List<DayForecast>) {
var _id: Long by map
var city: String by map
var country: String by map

constructor(id: Long, city: String, country: String,
dailyForecast: List<DayForecast>)
: this(HashMap(), dailyForecast) {
this._id = id
this.city = city
this.country = country
}
}

/**不同之处就是不需要设置id,因为它将
通过SQLite自增长*/
class DayForecast(val map: MutableMap<String, Any?>) {
var _id: Long by map
var date: Long by map
var description: String by map
var high: Long by map
var low: Long by map
var iconUrl: String by map
var cityId: Long by map

constructor(date: Long, description: String, high: Long,
low: Long, iconUrl: String, cityId: Long) : this(HashMap()) {
this.date = date
this.description = description
this.high = high
this.low = low
this.iconUrl = iconUrl
this.cityId = cityId
}
}

写入和查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ForecastDb(val forecastDbHelper: ForecastDbHelper = ForecastDbHelper.instance,
val dataMapper: DbDataMapper = DbDataMapper()) {

//dailyRequest 是查询语句中 where 的一部分。它是 whereSimple 函
//数需要的第一个参数,这与我们用一般的helper做的方式很相似
fun requestForecastByZipcode(zipCode: String, date: Date) = forecastDbHelper.use {
val dailyRequest = "${DayForecastTable.CITY_ID}=? " +
"AND ${DayForecastTable.DATE}>=?"
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode, date.toString())
.parseList { DayForecast(HashMap(it)) }

// 简化写法
// val dailyRequest = "${DayForecastTable.CITY_ID} = {id}" + "AND $
// {DayForecastTable.DATE} >= {date}"
// val dailyForecast = select(DayForecastTable.NAME)
// .where(dailyRequest, "id" to zipCode, "date" to date)
// .parseList { DayForecast(HashMap(it)) }
val city = select(CityForecastTable.NAME)
.whereSimple("${CityForecastTable.ID} = ?", zipCode)
.parseOpt { CityForecast(HashMap(it), dailyForecast) }
city?.let { dataMapper.convertToDomain(it) }
}

fun saveForecast(forecastList: ForecastList) = forecastDbHelper.use {
clear(DayForecastTable.NAME)
clear(CityForecastTable.NAME)
with(dataMapper.convertFromDomain(forecastList)) {
insert(CityForecastTable.NAME, *map.toVarargArray())
dailyForecast.forEach { insert(DayForecastTable.NAME, *it.map.toVarargArray()) }
}
}
}
  • DbDataMapper使用with简化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class DbDataMapper {

    fun convertFromDomain(forecastList: ForecastList) = with(forecastList) {
    val daily = dailyForecast.map { convertDayFromDomain(id, it) }
    CityForecast(id, city, country, daily)
    }

    private fun convertDayFromDomain(cityId: Long, forecast: Forecast) = with(forecast) {
    DayForecast(date, description, high, low, iconUrl, cityId)
    }

    fun convertToDomain(forecast: CityForecast) = with(forecast) {
    val daily = dailyForecast.map { convertDayToDomain(it) }
    ForecastList(_id, city, country, daily)
    }

    fun convertDayToDomain(dayForecast: DayForecast) = with(dayForecast) {
    Forecast(_id, date, description, high, low, iconUrl)
    }
    }
  • Map,SelectQueryBuilder 扩展函数 它需要一个表名和一个 vararg 修饰的 Pair<String, Any> 作为参数。这个函数会把 vararg 转换成Android SDK需要的 ContentValues 对象。所以任务组成是把 map 转换成一个 vararg 数组,在toVarargArray 函数结果前面使用 * 表示这个array会被分解成为一个 vararg 参数,这个在Java中是自动处理的,但是我们需要在Kotlin中明确指明,通过 map 的使用,可以用很简单的方式把类转换为数据表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    fun <K, V : Any> Map<K, V?>.toVarargArray(): Array<out Pair<K, V>> =
    map({ Pair(it.key, it.value!!) }).toTypedArray()

    fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T): List<T> =
    parseList(object : MapRowParser<T> {
    override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
    })

    fun <T : Any> SelectQueryBuilder.parseOpt(parser: (Map<String, Any?>) -> T): T? =
    parseOpt(object : MapRowParser<T> {
    override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
    })

    fun SQLiteDatabase.clear(tableName: String) {
    execSQL("delete from $tableName")
    }
willkernel wechat
关注微信公众号
帅哥美女们,请赐予我力量吧!