Kotlin for Android(七)

创建业务逻辑访问数据

从数据库获取数据
检查是否存在对应星期的数据
如果有,返回UI并且渲染
如果没有,请求服务器获取数据
结果被保存在数据库中并且返回UI渲染

  • 数据源应该是一个具体的实现,这样就可以被容易地修改,所以增加一些额外的代码,然后把 command 从数据访问中抽象出来听

    1
    2
    3
    4
    interface ForecastDataSource {
    fun requestForecastByZipCode(zipCode: Long, date: Long): For
    ecastList?
    }
  • 使用数据库的数据源和服务端数据源。顺序是很重要的,因为它会根据顺序去遍历这个sources,然后一旦获取到有效的返回值就会停止查询。逻辑顺序是先在本地查询(本地数据库中),然后再通过API查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class ForecastProvider(private val source: List<ForecastDataSource> = ForecastProvider.SOURCES) {
    companion object {
    val DAY_IN_MILLIS = 1000 * 60 * 60 * 24
    val SOURCES = listOf(ForecastDb(), ForecastServer())
    }

    fun requestByZipCode(zipCode: Long, days: Int): ForecastList = source.firstResult {
    requestSource(it, days, zipCode)
    }

    private fun requestSource(source: ForecastDataSource, days: Int, zipCode: Long): ForecastList? {
    val res = source.requestForecastByZipCode(zipCode, todayTimeSpan())
    return if (res != null && res.size >= days) res else null
    }

    private fun todayTimeSpan() = System.currentTimeMillis() / DAY_IN_MILLIS * DAY_IN_MILLIS
    }
  • 该函数接收一个断言函数,它接收一个 T 类型的对象然后返回一个 R? 类型的值。这表示 predicate 可以返回null类型,但是我们的 firstResult 不能返回null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    inline fun <T, R : Any> Iterable<T>.firstResult(predicate: (T) -
    > R?) : R {
    for (element in this){
    val result = predicate(element)
    if (result != null) return result
    }
    throw NoSuchElementException("No element matching predicate
    was found.")
    }
  • 请求服务端数据,ForecastDb保存数据到数据库,被重写的方法用来请求服务器,转换结果到 domain objects 并保存它们到数据库。它最后查询数据库返回数据,这是因为我们需要使用到插入到数据库中的字增长id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class ForecastServer(private val dataMapper: ServerDataMapper = ServerDataMapper(),
    private val forecastDb: ForecastDb = ForecastDb()) : ForecastDataSource {

    override fun requestDayForecast(id: Long) = throw UnsupportedOperationException()

    override fun requestForecastByZipCode(zipCode: Long, date: Long): ForecastList? {
    val result = ForecastRequest(zipCode.toString()).execute()
    val converted = dataMapper.convertToDomain(zipCode, result)
    forecastDb.saveForecast(converted)
    return forecastDb.requestForecastByZipCode(zipCode, date)
    }
    }
  • ServerDataMapper服务的返回的数据模型,映射为本地需要的data.model.Forecast

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class ServerDataMapper {
    fun convertToDomain(zipCode: Long, result: ForecastResult) = with(result) {
    ForecastList(zipCode, city.name, city.country, convertForecastListToDomain(list))
    }

    private fun convertForecastListToDomain(list: List<Forecast>): List<ModelForecast> {
    return list.mapIndexed { index, forecast ->
    val dt = Calendar.getInstance().timeInMillis + TimeUnit.DAYS.toMillis(index.toLong())
    convertForecastItemToDomain(forecast.copy(dt = dt))
    }
    }

    private fun convertForecastItemToDomain(forecast: Forecast) = with(forecast) {
    ModelForecast(-1, dt, weather[0].description, temp.max.toInt(),
    temp.min.toInt(), generateIconUrl(weather[0].icon))
    }

    private fun generateIconUrl(iconCode: String) = "http://openweathermap.org/img/w/$iconCode.png"
    }
  • ForecastCommand 不会再直接与服务端交互,也不会转换数据到 domain model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    RequestForecastCommand(val zipCode: Long,
    val forecastProvider: ForecastProvider = ForecastProvide
    r()) :
    Command<ForecastList> {
    companion object {
    val DAYS = 7
    }
    override fun execute(): ForecastList {
    return forecastProvider.requestByZipCode(zipCode, DAYS)
    }
    }

FlowControl ranges

  • If表达式 if 表达式总是返回一个value。如果一个分支返回了Unit,那整个表达式也将返回Unit,它是可以被忽略的

    1
    2
    3
    4
    val z = if (condition) x else y

    val z1 = if (v1 > 3 && v1 < 7) v1 else 0
    val z1 = if (v1 in 4..6) v1 else 0
  • When表达式 对于默认的选项,我们可以增加一个 else 分支,它会在前面没有任何条件匹配时再执行。条件匹配成功后执行的代码也可以是代码块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    when (x){
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> {
    print("I'm a block")
    print("x is neither 1 nor 2")
    }
    }

    也可以返回一个值,条件可以被逗号分割
    val result = when (x) {
    0, 1 -> "binary"
    else -> "error"
    }
  • 检测参数类型并进行判断,参数会被自动转型,所以你不需要去明确地做类型转换

    1
    2
    3
    4
    5
    6
    when (view) {
    is TextView -> view.setText("I'm a TextView")
    is EditText -> toast("EditText value: ${view.getText()}")
    is ViewGroup -> toast("Number of children: ${view.childCount} ")
    else -> textView.visibility = View.GONE
    }
  • 检测参数范围

    1
    2
    3
    4
    5
    6
    7
    val cost = when(x) {
    in 1..10 -> "cheap"
    in 10..100 -> "regular"
    in 100..1000 -> "expensive"
    in specialValues -> "special value!"
    else -> "not rated"
    }
  • 合并使用

    1
    2
    3
    4
    5
    6
    valres=when{
    x in 1..10 -> "cheap"
    s.contains("hello") -> "it's a welcome!"
    v is ViewGroup -> "child count: ${v.getChildCount()}"
    else -> ""
    }
  • For循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for (item in collection) {
    print(item)
    }

    for (index in 0..viewGroup.getChildCount() - 1) {
    val view = viewGroup.getChildAt(index)
    view.visibility = View.VISIBLE
    }

    for (i in array.indices)
    print(array[i])
  • While do/while

    1
    2
    3
    4
    5
    6
    7
    8
    while(x > 0){
    x--
    }
    do{
    val y = retrieveData()
    } while (y != null) // y在这里是可见的!
    While和do/while循环
    156
  • Range 表达式使用一个 .. 操作符,它是被定义实现了一个 RangTo 方法

    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
            if(i >= 0 && i <= 10)
    println(i)

    if (i in 0..10)
    println(i)

    for (i in 0..10)
    println(i)

    // Ranges 默认会自增长,所以如果像以下的代码
    for (i in 10..0)
    println(i)

    // 可以使用 downTo 函数
    for(i in 10 downTo 0)
    println(i)

    // 在 range 中使用 step 来定义一个从1到一个值的不同的空隙
    for (i in 1..4 step 2) println(i)
    for (i in 4 downTo 1 step 2) println(i)

    // 创建一个open range(不包含最后一项,译者注:类似数学中的开区间),你可以使用 until 函数
    // 使用 (i in 0 until list.size) 比 (i in 0..list.size - 1) 更加容易理解
    for (i in 0 until 4) println(i)

    // val views = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) }

泛型

基础
  • 创建一个指定泛型类,这个类现在可以使用任何的类型初始化,并且参数也会使用定义的类型

    1
    2
    3
    4
    5
    6
    7
    class TypedClass<T>(parameter: T) {
    val value: T = parameter
    }

    val t1 = TypedClass("Hello World!")
    val t2 = TypedClass(25)
    val t3 = TypedClass<String?>(null) 接收一个null引用,那仍然还是需要指定它的类型
  • 限制上一个类中为非null类型:

    1
    2
    3
    class TypedClass<T : Any>(parameter: T) {
    val value: T = parameter
    }
  • 如果我们只希望使用 Context 的子类

    1
    2
    3
    class TypedClass<T : Context>(parameter: T) {
    val value: T = parameter
    }
  • 构建泛型函数:

    1
    2
    3
    fun <T> typedFunction(item: T): List<T> {
    ...
    }
变体
  • 增加一个 Integer 到 Object List,编译不通过

    1
    2
    3
    4
    List<String> strList = new ArrayList<>();
    List<Object> objList = strList;
    objList.add(5);
    String str = objList.get(0);
  • 以下编译通过,因为Collection 接口中的 void addAll(Collection<? extends E> items);

    1
    2
    3
    List<String> strList = new ArrayList<>();
    List<Object> objList = new ArrayList<>();
    objList.addAll(strList);
  • 增加 Strings 到另一个集合中唯一的限制就是那个集合接收 Strings 或者父类

    1
    2
    3
    4
    void copyStrings(Collection<? super String> to, Collection<Strin
    g> from) {
    to.addAll(from);
    }
  • Kotlin仅仅使用 out 来针对协变( covariance )和使用 in 来针对逆变( contravariance )。在这个例子中,当我们类产生的对象可以被保存到弱限制的变量中,我们使用协变。我们可以直接在类中定义声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class TypedClass<out T>() {
    fun doSomething(): T {
    ...
    }
    }

    这就是所有我们需要的。现在,在Java中不能编译的代码在Kotlin中可以完美运行:
    val t1 = TypedClass<String>()
    val t2: TypedClass<Any> = t1
泛型例子
  • let 它可以被任何对象调用。它接收一个函数(接收一个对象,返回函数结果)作为参数

    1
    2
    3
    4
    5
    6
    public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
    }
  • 替换if (forecast != null) dataMapper.convertDayToDomain(forecast) else nullforecast?.let { dataMapper.convertDayToDomain(it) }

  • with 接收一个对象和一个函数,这个函数会作为这个对象的扩展函数执行。这表示我们根据推断可以在函数内使用 this,函数通过 f: T.() -> R 声明被定义成了扩展函数这就是为什么我们可以调用 receiver.f()

    1
    inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
1
2
3
4
5
fun convertFromDomain(forecast: ForecastList) = with(forecast) {
val daily = dailyForecast map { convertDayFromDomain(id, it)
}
CityForecast(id, city, country, daily)
}
  • apply 它看起来于with 很相似,但是是有点不同之处。 apply 可以避免创建builder的方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,只需要一个泛型类型,因为调用这个函数的对象也就是这个函数返回的对象

    1
    inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
  • apply 示例创建了一个 TextView ,修改了一些属性,然后赋值给一个变量

    1
    2
    3
    4
    5
    val textView = TextView(context).apply {
    text = "Hello"
    hint = "Hint"
    textColor = android.R.color.white
    }
  • 在 ToolbarManager 中,我们使用这种方式来创建导航drawable:

    1
    2
    3
    4
    5
    private fun createUpDrawable() = with(DrawerArrowDrawable(toolba
    r.ctx)) {
    progress = 1f
    this
    }
  • 使用 with 和返回 this 是非常清晰的,但是使用 apply 可以更加简单:

    1
    2
    3
    4
    private fun createUpDrawable() = DrawerArrowDrawable(toolbar.ctx)
    .apply {
    progress = 1f
    }

其它概念

  • 内部类 如果它是一个通常的类,它不能去访问外部类的成员,如果需要访问外部类的成员,我们需要用 inner 声明这个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Outer1{
    private val bar:Int=1
    class Nested{
    fun foo()=2
    }
    }
    val d1=Outer1.Nested().foo()

    class Outer2{
    private val bar:Int=1
    inner class Nested{
    fun foo()=bar
    }
    }
    val d2=Outer2().Nested().foo()
  • 枚举 可以带参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    enum class Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY
    }

    enum class Icon(val res: Int) {
    UP(R.drawable.ic_up),
    SEARCH(R.drawable.ic_search),
    CAST(R.drawable.ic_cast)
    }
    val searchIconRes = Icon.SEARCH.res


    枚举可以通过 String 匹配名字来获取,我们也可以获取包含所有枚举
    的 Array ,所以我们可以遍历它
    val search: Icon = Icon.valueOf("SEARCH")
    val iconList: Array<Icon> = Icon.values()


    而且每一个枚举都有一些函数来获取它的名字、声明的位置
    val searchName: String = Icon.SEARCH.name()
    val searchPosition: Int = Icon.SEARCH.ordinal()
  • 密封(Sealed)类 类似Scala中的 Option 类:这种类型可以防止null的使用,当对象包含一个值时返回 Some 类,当对象为空时则返回 None

    1
    2
    3
    4
    sealed class Option<out T> {
    class Some<out T> : Option<T>()
    object None : Option<Nothing>()
    }
  • 有一件关于密封类很不错的事情是当我们使用 when 表达式时,我们可以匹配所有选项而不使用 else 分支

    1
    2
    3
    4
    val result = when (option) {
    is Option.Some<*> -> "Contains a value"
    is Option.None -> "Empty"
    }
  • 异常 在Kotlin中,所有的 Exception 都是实现了 Throwable ,含有一个 message 且未经检查。这表示我们不会强迫我们在任何地方使用 try/catch 。这与Java中不太一样,比如在抛出 IOException 的方法,我们需要使用 try-catch 包围代码块。通过检查exception来处理显示并不是一个好的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    抛出异常的方式与Java很类似:
    throw MyException("Exception message")
    try 表达式也是相同的:
    try{
    // 一些代码
    }
    catch (e: SomeException) {
    // 处理
    }
    finally {
    // 可选的finally块
    }
  • 在Kotlin中,throw和try都是表达式,这意味着它们可以被赋值给一个变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
     val s = when(x){
    is Int -> "Int instance"
    is String -> "String instance"
    else -> throw UnsupportedOperationException("Not valid type"
    )
    }

    val s = try { x as String } catch(e: ClassCastException) { null
    }
willkernel wechat
关注微信公众号
帅哥美女们,请赐予我力量吧!