创建业务逻辑访问数据
从数据库获取数据
检查是否存在对应星期的数据
如果有,返回UI并且渲染
如果没有,请求服务器获取数据
结果被保存在数据库中并且返回UI渲染
数据源应该是一个具体的实现,这样就可以被容易地修改,所以增加一些额外的代码,然后把 command 从数据访问中抽象出来听
1
2
3
4interface 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
17class 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
9inline 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
12class 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.Forecast1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class 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
11RequestForecastCommand(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
4val 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 0When表达式 对于默认的选项,我们可以增加一个 else 分支,它会在前面没有任何条件匹配时再执行。条件匹配成功后执行的代码也可以是代码块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14when (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
6when (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
7val 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
6valres=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
11for (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
8while(x > 0){
x--
}
do{
val y = retrieveData()
} while (y != null) // y在这里是可见的!
While和do/while循环
156Range 表达式使用一个 .. 操作符,它是被定义实现了一个 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
26if(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
7class 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
3class TypedClass<T : Any>(parameter: T) {
val value: T = parameter
}如果我们只希望使用 Context 的子类
1
2
3class TypedClass<T : Context>(parameter: T) {
val value: T = parameter
}构建泛型函数:
1
2
3fun <T> typedFunction(item: T): List<T> {
...
}
变体
增加一个 Integer 到 Object List,编译不通过
1
2
3
4List<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
3List<String> strList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
objList.addAll(strList);增加 Strings 到另一个集合中唯一的限制就是那个集合接收 Strings 或者父类
1
2
3
4void 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
9class TypedClass<out T>() {
fun doSomething(): T {
...
}
}
这就是所有我们需要的。现在,在Java中不能编译的代码在Kotlin中可以完美运行:
val t1 = TypedClass<String>()
val t2: TypedClass<Any> = t1
泛型例子
let 它可以被任何对象调用。它接收一个函数(接收一个对象,返回函数结果)作为参数
1
2
3
4
5
6public 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 null
为forecast?.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 | fun convertFromDomain(forecast: ForecastList) = with(forecast) { |
apply 它看起来于
with
很相似,但是是有点不同之处。apply
可以避免创建builder
的方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,只需要一个泛型类型,因为调用这个函数的对象也就是这个函数返回的对象1
inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
apply 示例创建了一个 TextView ,修改了一些属性,然后赋值给一个变量
1
2
3
4
5val textView = TextView(context).apply {
text = "Hello"
hint = "Hint"
textColor = android.R.color.white
}在 ToolbarManager 中,我们使用这种方式来创建导航drawable:
1
2
3
4
5private fun createUpDrawable() = with(DrawerArrowDrawable(toolba
r.ctx)) {
progress = 1f
this
}使用 with 和返回 this 是非常清晰的,但是使用 apply 可以更加简单:
1
2
3
4private fun createUpDrawable() = DrawerArrowDrawable(toolbar.ctx)
.apply {
progress = 1f
}
其它概念
内部类 如果它是一个通常的类,它不能去访问外部类的成员,如果需要访问外部类的成员,我们需要用 inner 声明这个类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 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
22enum 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
4sealed class Option<out T> {
class Some<out T> : Option<T>()
object None : Option<Nothing>()
}有一件关于密封类很不错的事情是当我们使用 when 表达式时,我们可以匹配所有选项而不使用 else 分支
1
2
3
4val 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
9val 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
}