├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── easy
│ │ └── kotlin
│ │ └── mytodoapplication
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── easy
│ │ │ └── kotlin
│ │ │ └── mytodoapplication
│ │ │ └── TAG.java
│ ├── kotlin
│ │ └── com
│ │ │ └── easy
│ │ │ └── kotlin
│ │ │ └── mytodoapplication
│ │ │ ├── MainActivity.kt
│ │ │ ├── MyTodoApplication.kt
│ │ │ ├── TodoAdapter.kt
│ │ │ ├── TodoEditFragment.kt
│ │ │ ├── TodosFragment.kt
│ │ │ └── model
│ │ │ └── Todo.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_content_add.png
│ │ ├── drawable-mdpi
│ │ └── ic_content_add.png
│ │ ├── drawable-xhdpi
│ │ └── ic_content_add.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_content_add.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_content_add.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── content_main.xml
│ │ ├── empty_view.xml
│ │ ├── fragment_todos.xml
│ │ └── todo_item.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── easy
│ └── kotlin
│ └── mytodoapplication
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 第13章 使用 Kotlin 和 Anko 的Android 开发
2 | ===
3 |
4 |
5 | ## 13.1 什么是 Anko?
6 |
7 | Anko (https://github.com/Kotlin/anko) 是一个用 Kotlin 写的Android DSL (Domain-Specific Language)。长久以来,Android视图都是用 XML 来完成布局的。这些 XML可重用性比较差。同时在运行的时候,XML 要转换成 Java 表述,这在一定程度上占用了 CPU 和耗费了电量。
8 |
9 | Anko是一个 Kotlin 库, 它使 android 应用程序的开发变得更快、更容易。它使您的代码干净, 易于阅读, 并让您忘记了粗糙的边缘 android sdk 为 java。
10 |
11 | Anko由几个部分组成:
12 |
13 | |模块| 功能说明|
14 | |---|---|
15 | |Anko Commons| 使得对 intents, dialogs, logging等操作更加简单的轻量级库|
16 | |Anko Layouts| 快速和类型安全的动态的 android 布局库|
17 | |Anko SQLite| 用于 android sqlite 的查询 dsl 和分析库|
18 | |Anko Coroutines| 基于 kotlinx 协程库|
19 |
20 | 有了Anko 我们就能直接用 Kotlin 在任何的 Activity 、 Fragment 或者 AnkoComponent里来编写视图。
21 |
22 | ## 13.2 一个简单Anko视图
23 |
24 | 这里是一个转换成 Anko 的简单 XML 文件。
25 |
26 | XML
27 | ```
28 |
32 |
37 |
41 |
42 | ```
43 |
44 | 用 Anko 描述的同样的视图
45 |
46 | ```
47 | verticalLayout {
48 | var title = editText {
49 | id = R.id.todo_title
50 | hintResource = R.string.title_hint
51 | }
52 | button {
53 | textResource = R.string.add_todo
54 | onClick { view -> {
55 | // 可以在这里添加一些处理逻辑
56 | title.text = "Foo"
57 | }
58 | }
59 | }
60 | }
61 | ```
62 |
63 | 可以看到在button布局中的onClick监听函数中,因为我们是使用 Kotlin代码来设计视图,所以可以直接使用title变量(editText视图对象)。
64 |
65 | ## 13.3 快速入门实例
66 |
67 | 下面我们通过一个“我的日程”待办事项应用,来详细介绍使用 Kotlin 混合 Java,使用 Anko 开发的Android 应用的方法。移动端数据库引擎我们使用 Realm,视图绑定使用Butter Knife。
68 |
69 | 这个应用程序界面如下所示:
70 |
71 |
72 | 
73 |
74 | 
75 |
76 | 
77 |
78 |
79 |
80 |
81 | ## 13.4 使用 Android Studio 新建工程
82 |
83 | 我们首先在 Android Studio 中新建工程,步骤如下:
84 |
85 | 第一步,新建项目
86 |
87 | 
88 |
89 | 第二步,配置项目基本信息
90 |
91 | 
92 |
93 | 第三步,设置支持设备以及 SDK 版本
94 |
95 | 
96 |
97 | 第四步,选择 Basic Activity
98 |
99 | 
100 |
101 |
102 | 第五步,使用默认的Activity命名
103 |
104 | 
105 |
106 |
107 | 我们将得到一个标准的 Gradle Android 工程:
108 |
109 |
110 | 
111 |
112 |
113 | 其中,app 工程 src 目录如下:
114 |
115 | ```
116 | .
117 | ├── androidTest
118 | │ └── java
119 | │ └── com
120 | │ └── easy
121 | │ └── kotlin
122 | │ └── mytodoapplication
123 | │ └── ExampleInstrumentedTest.java
124 | ├── main
125 | │ ├── AndroidManifest.xml
126 | │ ├── java
127 | │ │ └── com
128 | │ │ └── easy
129 | │ │ └── kotlin
130 | │ │ └── mytodoapplication
131 | │ │ └── MainActivity.java
132 | │ └── res
133 | │ ├── drawable
134 | │ ├── layout
135 | │ │ ├── activity_main.xml
136 | │ │ └── content_main.xml
137 | │ ├── menu
138 | │ │ └── menu_main.xml
139 | │ ├── mipmap-hdpi
140 | │ │ ├── ic_launcher.png
141 | │ │ └── ic_launcher_round.png
142 | │ ├── mipmap-mdpi
143 | │ │ ├── ic_launcher.png
144 | │ │ └── ic_launcher_round.png
145 | │ ├── mipmap-xhdpi
146 | │ │ ├── ic_launcher.png
147 | │ │ └── ic_launcher_round.png
148 | │ ├── mipmap-xxhdpi
149 | │ │ ├── ic_launcher.png
150 | │ │ └── ic_launcher_round.png
151 | │ ├── mipmap-xxxhdpi
152 | │ │ ├── ic_launcher.png
153 | │ │ └── ic_launcher_round.png
154 | │ └── values
155 | │ ├── colors.xml
156 | │ ├── dimens.xml
157 | │ ├── strings.xml
158 | │ └── styles.xml
159 | └── test
160 | └── java
161 | └── com
162 | └── easy
163 | └── kotlin
164 | └── mytodoapplication
165 | └── ExampleUnitTest.java
166 |
167 | 28 directories, 21 files
168 |
169 | ```
170 |
171 | 我们直接在Android 模拟器中(也可以选择用真机)运行它,可以看到如下效果:
172 |
173 |
174 | 
175 |
176 |
177 | ## 13.5 设计UI 界面主题颜色
178 |
179 |
180 | 我们首先把应用名称改成“我的日程”。在文件MyTodoApplication/app/src/main/res/values/strings.xml中:
181 |
182 | ```
183 |
184 | MyTodoApplication
185 | Settings
186 |
187 |
188 | ```
189 |
190 | 改写成
191 |
192 | ```
193 |
194 | 我的日程
195 | 设置
196 |
197 |
198 | ```
199 |
200 |
201 | 再去colors.xml中,设计主题颜色为:
202 | ```
203 |
204 |
205 | #f2fced
206 | #456a7c
207 | #8fb3c4
208 |
209 |
210 | ```
211 |
212 |
213 | 然后到文件MyTodoApplication/app/src/main/res/layout/activity_main.xml中,设置android.support.v7.widget.Toolbar的背景色为
214 | ```
215 | android:background="?attr/colorPrimaryDark"
216 | ```
217 |
218 | 配置android.support.design.widget.FloatingActionButton的图标为:
219 | ```
220 | app:srcCompat="drawable/ic_content_add"
221 | ```
222 |
223 | 其中,ic_content_add.png图片是我们添加按钮中间的加号 icon。
224 |
225 |
226 | ## 13.6 配置 Kotlin 与 Anko 依赖
227 |
228 | 我们默认生成的 app 项目的 Gradle 配置文件build.gradle如下:
229 | ```
230 | apply plugin: 'com.android.application'
231 |
232 | android {
233 | compileSdkVersion 25
234 | buildToolsVersion "25.0.3"
235 | defaultConfig {
236 | applicationId "com.easy.kotlin.mytodoapplication"
237 | minSdkVersion 21
238 | targetSdkVersion 25
239 | versionCode 1
240 | versionName "1.0"
241 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
242 | }
243 | buildTypes {
244 | release {
245 | minifyEnabled false
246 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
247 | }
248 | }
249 | }
250 |
251 | dependencies {
252 | compile fileTree(dir: 'libs', include: ['*.jar'])
253 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
254 | exclude group: 'com.android.support', module: 'support-annotations'
255 | })
256 | compile 'com.android.support:appcompat-v7:25.3.1'
257 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
258 | compile 'com.android.support:design:25.3.1'
259 | testCompile 'junit:junit:4.12'
260 | }
261 |
262 | ```
263 |
264 | 下面我们在 app 项目的build.gradle里面加上Kotlin 、Anko 、Realm、Butter Knife 等依赖。
265 |
266 | ### 13.6.1 Kotlin依赖
267 |
268 | 首先,启用插件`kotlin-android` :
269 | ```
270 | apply plugin: 'kotlin-android'
271 | ```
272 | 然后,添加构建脚本
273 | ```
274 | buildscript {
275 |
276 | }
277 | ```
278 | 我们使用 Kotlin 1.1.3版本。在构建脚本中添加kotlin-gradle-plugin依赖,使用 Kotlin 对应的版本号。
279 | ```
280 | buildscript {
281 | ext.kotlin_version = '1.1.3'
282 | repositories {
283 | mavenCentral()
284 | }
285 | dependencies {
286 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
287 | }
288 | }
289 | ```
290 | 在项目依赖里添加 Kotlin 标准库:
291 | ```
292 | // Kotlin
293 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
294 | ```
295 |
296 |
297 | ### 13.6.2 添加 Kotlin 源代码目录
298 |
299 | 首先,我们在 src/main/下面新建一个 kotlin 目录,来存放 Kotlin源码。然后在 build.gradle 文件里的 `android {}` 配置里面添加Java的编译路径:
300 |
301 | ```
302 | android {
303 | ...
304 | sourceSets {
305 | // += , 在main中创建kotlin文件夹, 用于存放kotlin代码
306 | main.java.srcDirs += 'src/main/kotlin'
307 | }
308 | }
309 | ```
310 |
311 | 刚添加完毕,src/main/kotlin 还没有变成源码目录的蓝色,这个时候点击下图右上角的 Sync Now :
312 |
313 |
314 | 
315 |
316 | Gradle 同步完毕,即可看到kotlin 目录已经变成蓝色的源码目录了:
317 |
318 |
319 | 
320 |
321 |
322 |
323 |
324 |
325 |
326 | ### 13.6.3 Anko依赖
327 |
328 | 在项目依赖里添加
329 | ```
330 | // Anko
331 | compile 'org.jetbrains.anko:anko-sdk15:0.8.2' // sdk19, sdk21, sdk23 are also available
332 | compile 'org.jetbrains.anko:anko-support-v4:0.8.2' // In case you need support-v4 bindings
333 | compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.2' // For appcompat-v7 bindings
334 |
335 | ```
336 |
337 | ### 13.6.4 Realm依赖
338 |
339 | ```
340 | compile 'io.realm:realm-android:0.87.1'
341 | compile 'com.github.thorbenprimke:realm-recyclerview:0.9.12' // 在jitpack.io上
342 | ```
343 |
344 | 其中,Realm是一个轻量级的跨平台移动数据库引。Realm 简单易用,model 设计在代码中,更加易于维护,同时其性能也不错。在Android开发中,它可以替代 SQLite 和 ORM 框架。相比SQLite,Realm更快并且具有很多现代数据库的特性,比如支持JSON,流式api,数据变更通知,以及加密支持。
345 |
346 | RecyclerView用于在有限的窗口展现大量的数据,相比ListView、GridView,RecyclerView标准化了ViewHolder,而且更加灵活,可以轻松实现ListView实现不了的样式和功能。我们使用的`com.github.thorbenprimke:realm-recyclerview` 依赖包在在jitpack.io上, 所以我们还需要配置一下仓库地址:
347 |
348 | ```
349 | repositories {
350 | mavenCentral()
351 | maven { url "https://jitpack.io" }
352 | }
353 | ```
354 |
355 |
356 | 提示:realm-recyclerview的 Github 地址是 https://github.com/thorbenprimke/realm-recyclerview
357 |
358 | 另外, Kotlin使用 Realm 还要加上注解处理的依赖库:
359 | ```
360 | // kotlin使用realm的注解处理依赖库
361 | kapt "io.realm:realm-annotations:0.87.1"
362 | kapt "io.realm:realm-annotations-processor:0.87.1"
363 | ```
364 |
365 |
366 |
367 |
368 |
369 | ### 13.6.5 Butter Knife依赖
370 |
371 | Butter Knife是基于注解处理方式工作:通过对代码注解自动生成模板代码。我们添加其依赖如下:
372 |
373 | ```
374 | // Butter Knife,专门为Android View设计的绑定注解,专业解决各种findViewById
375 | compile 'com.jakewharton:butterknife:8.7.0'
376 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'
377 | ```
378 |
379 | Butter Knife主要是用来做Android视图的成员变量和属性的数据绑定。在开发过程中,我们通常要写大量的findViewById和点击事件,像初始view、设置view监听这样简单而重复的操作会显得比较繁琐。而我们有了 Butter Knife,就可以通过使用注解直接生成样板代码。例如,在 Java 中我们可以通过在字段上使用 @BindView 来替代 findViewById 的调用。上面的配置中的`annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'`就是来处理这些注解从而生成样板代码的。
380 |
381 |
382 | ```
383 | @Bind(R.id.todo_item_todo_title)
384 | public TextView todoTitle;
385 |
386 | @Bind(R.id.todo_item_todo_content)
387 | public TextView todoContent;
388 | ```
389 |
390 | 而在 Kotlin 中使用Butter Knife情况有些不同,需要作额外的配置。
391 |
392 | 如果在Kotlin中直接使用ButterKnife的注解方式的话,会出现空指针的异常,导致绑定失败。例如
393 | ```
394 | @Bind(R.id.todos_recycler_view)
395 | var realmRecyclerView: RealmRecyclerView? = null
396 | ```
397 | 运行会报错:
398 | ```
399 | Caused by: kotlin.KotlinNullPointerException
400 | at com.easy.kotlin.mytodoapplication.TodoListFragment.onResume(TodoListFragment.kt:43)
401 | ```
402 |
403 | 一般情况下,我们使用Kotlin集成 Java 生态的一些框架的时候,像 Spring Boot,JPA,Butter Knife,Realm等,都需要一些额外的插件或者依赖来“填充缝隙”(例如:all-open, kotterknife,realm-annotations等), 所谓Kotlin 与 Java 的无缝集成,很多时候并非Java 中怎么用,Kotlin就直接拿过来就怎么用,往往是要再添加一些插件或者额外的配置等。
404 |
405 |
406 | 那么要如何才能在Kotlin的环境中使用ButterKnife呢?
407 |
408 | 在早些时候,ButterKnife的作者已经帮我们想好解决方案了,那就是——KotterKnife,见名知意。KotterKnife的GitHub地址是:https://github.com/JakeWharton/kotterknife 。这个插件是建立在ButterKnife 7的基础上的。
409 |
410 |
411 | 下面我们配置一下在 Kotlin 中使用 Butter Knife 的依赖库 KotterKnife。
412 |
413 | 首先在repositories中添加KotterKnife的仓库地址(KotterKnife不在 Maven Center 仓库中,而是在oss.sonatype.org仓库中。这么多仓库,要是哪天能统一用一个就方便多了)。
414 | ```
415 | repositories {
416 | ...
417 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
418 | }
419 | ```
420 | 然后在dependencies里面添加依赖
421 | ```
422 | dependencies {
423 | ...
424 | compile 'com.jakewharton:butterknife:7.0.1'
425 | compile 'com.jakewharton:kotterknife:0.1.0-SNAPSHOT'
426 | }
427 | ```
428 |
429 | 采用这种方式的配置,我们的视图注入代码如下
430 | ```
431 | val todoTitle: TextView by bindView(R.id.todo_item_todo_title)
432 | val todoContent: TextView by bindView(R.id.todo_item_todo_content)
433 | ```
434 |
435 | 这样的代码看起来不是那么的优雅,还没有在 Java 中直接使用注解来的简单好看。同时要注意的是,如果使用 kotterknife 0.1.0 + butterknife:7.0.1 ,同时使用 Java 跟 Kotlin 混合编程的场景中使用 Butter Knife,发现配了KotterKnife 之后的 Java 的注解式写法就失效了。也就是说,如果我们上面添加了KotterKnife的依赖,那么 Java 代码中同时使用 Butter Knife 注解的地方会绑定失败。不过这个问题,在后面的新版本中已经解决。例如在butterknife 8.7.0中,我们可以直接添加下面的依赖项:
436 |
437 | ```
438 | compile 'com.jakewharton:butterknife:8.7.0'
439 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'
440 | kapt 'com.jakewharton:butterknife-compiler:8.7.0'
441 | ```
442 | 其中,`annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'` 是 Java 的butterknife注解处理器。` kapt 'com.jakewharton:butterknife-compiler:8.7.0'` 是 Kotlin 的butterknife注解处理器(Kotlin Annotation processing tool,kapt)。
443 |
444 | 这样我们的代码就继续优雅简洁下去了:
445 |
446 | ```
447 | @BindView(R.id.todo_item_todo_title)
448 | lateinit var todoTitle: TextView
449 | @BindView(R.id.todo_item_todo_content)
450 | lateinit var todoContent: TextView
451 | ```
452 |
453 | 其中,lateinit 修饰符允许声明非空类型,并在对象创建后(构造函数调用后)初始化。 不使用 lateinit 则需要声明可空类型并且有额外的空安全检测操作。
454 |
455 | 当然,我们使用 Butter Knife 的同时,仍然可以使用原生的 findViewById :
456 |
457 | ```
458 | class MainActivity : AppCompatActivity() {
459 | var fab: FloatingActionButton? = null
460 |
461 | override fun onCreate(savedInstanceState: Bundle?) {
462 | super.onCreate(savedInstanceState)
463 | setContentView(R.layout.activity_main)
464 |
465 | val toolbar = findViewById(R.id.toolbar) as Toolbar
466 | setSupportActionBar(toolbar)
467 |
468 | fab = findViewById(R.id.fab) as FloatingActionButton
469 |
470 |
471 | // 添加日程事件
472 | fab?.setOnClickListener { _ ->
473 | ...
474 | hideFab()
475 | }
476 | }
477 |
478 | fun hideFab() {
479 | fab?.visibility = View.GONE
480 | }
481 |
482 | fun showFab() {
483 | fab?.visibility = View.VISIBLE
484 | }
485 |
486 | }
487 | ```
488 |
489 |
490 |
491 | ## 13.7 将MainActivity.java 转成 Kotlin 代码
492 |
493 | 选中默认生成的MainActivity.java, 我们使用 IDEA 的 Code > Convert Java File to Kotlin File :
494 |
495 | 
496 |
497 |
498 | 点击转换,即可看到转换成 Kotlin 的代码:
499 |
500 | ```
501 | package com.easy.kotlin.mytodoapplication
502 |
503 | import android.os.Bundle
504 | import android.support.design.widget.FloatingActionButton
505 | import android.support.design.widget.Snackbar
506 | import android.support.v7.app.AppCompatActivity
507 | import android.support.v7.widget.Toolbar
508 | import android.view.Menu
509 | import android.view.MenuItem
510 |
511 | class MainActivity : AppCompatActivity() {
512 |
513 | override fun onCreate(savedInstanceState: Bundle?) {
514 | super.onCreate(savedInstanceState)
515 | setContentView(R.layout.activity_main)
516 | val toolbar = findViewById(R.id.toolbar) as Toolbar
517 | setSupportActionBar(toolbar)
518 |
519 | val fab = findViewById(R.id.fab) as FloatingActionButton
520 | fab.setOnClickListener { view ->
521 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
522 | .setAction("Action", null).show()
523 | }
524 | }
525 |
526 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
527 | // Inflate the menu; this adds items to the action bar if it is present.
528 | menuInflater.inflate(R.menu.menu_main, menu)
529 | return true
530 | }
531 |
532 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
533 | // Handle action bar item clicks here. The action bar will
534 | // automatically handle clicks on the Home/Up button, so long
535 | // as you specify a parent activity in AndroidManifest.xml.
536 | val id = item.itemId
537 |
538 |
539 | if (id == R.id.action_settings) {
540 | return true
541 | }
542 |
543 | return super.onOptionsItemSelected(item)
544 | }
545 | }
546 |
547 | ```
548 |
549 |
550 | 看,这就是Android 开发者,从 Java无缝转到 Kotlin 的过程。
551 |
552 | 我们把这个MainActivity.kt放到对应的 src/main/kotlin 目录下。首先新建`package com.easy.kotlin.mytodoapplication` , 直接在 IDEA 中把这个MainActivity.kt 拖到这个package 下面即可。现在我们的工程目录是下面这个样子
553 |
554 | 
555 |
556 |
557 |
558 |
559 | ## 13.8 在 Kotlin 中使用 Realm
560 |
561 | 我们需要添加针对 Kotlin 的realm注解处理的库:
562 |
563 | ```
564 | kapt "io.realm:realm-annotations:0.87.1"
565 | kapt "io.realm:realm-annotations-processor:0.87.1"
566 | ```
567 |
568 |
569 | ## 13.9 添加日程实体类
570 |
571 | 我们先从领域模型的建立开始。首先我们需要设计一个极简的待办事项的实体类 Todo, 它有主键 id、标题、内容三个字段。
572 |
573 | ```
574 | @RealmClass
575 | open class Todo : RealmObject() {
576 | @PrimaryKey
577 | open var id: String = "-1"
578 | open var title: String = "日程"
579 | open var content: String = "事项"
580 | }
581 | ```
582 |
583 | 然后,我们写一个应用程序入口类`MyTodoApplication`继承`android.app.Application`, 在 onCreate() 里面初始化 Realm 数据库的配置。代码如下:
584 |
585 | ```
586 | class MyTodoApplication : Application() {
587 | override fun onCreate() {
588 | super.onCreate()
589 |
590 | val config = RealmConfiguration.Builder(this)
591 | .name("realm.my_todos")// 库文件名
592 | .encryptionKey(getKey()) // 加密
593 | .schemaVersion(1) // 版本号
594 | .deleteRealmIfMigrationNeeded()
595 | .build()
596 |
597 | Realm.setDefaultConfiguration(config)// 设置默认 RealmConfiguration
598 |
599 | }
600 |
601 | /**
602 | * 64 bits
603 | * @return
604 | */
605 | private fun getKey(): ByteArray {
606 | return byteArrayOf(0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1)
607 | }
608 | }
609 | ```
610 |
611 | RealmConfiguration.Builder里面如果没有deleteRealmIfMigrationNeeded()的话,会如下报错误:
612 |
613 | ```
614 | Caused by: io.realm.exceptions.RealmMigrationNeededException:
615 | RealmMigration must be provided ...
616 | at com.easy.kotlin.mytodoapplication.TodoListFragment.onActivityCreated(TodoListFragment.kt:36)
617 | ```
618 |
619 |
620 | 提示: 更多关于 realm 数据库的相关内容可参考 https://realm.io/docs/
621 |
622 |
623 |
624 | ## 13.10 添加日程事件
625 |
626 | 现在我们点击添加日程的浮层按钮中,添加切换到 “日程添加编辑” `TodoEditFragment`的逻辑。
627 |
628 | ```
629 | // 添加日程事件
630 | fab?.setOnClickListener { _ ->
631 | // Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show()
632 | val todoEditFragment = TodoEditFragment()
633 | getSupportFragmentManager()
634 | .beginTransaction()
635 | .replace(R.id.content_main, todoEditFragment, todoEditFragment.javaClass.getSimpleName())
636 | .addToBackStack(todoEditFragment.javaClass.getSimpleName())
637 | .commit()
638 |
639 | hideFab()
640 | }
641 | ```
642 |
643 | ## 13.11 添加日程界面
644 |
645 | 下面我们来完成这个添加日程的界面。
646 |
647 | 
648 |
649 |
650 | 我们采用Fragment来实现。首先新建一个TodoEditFragment继承Fragment() :
651 | ```
652 | class TodoEditFragment : Fragment() {
653 | val realm: Realm = Realm.getDefaultInstance()
654 | var todo: Todo? = null
655 |
656 | companion object {
657 | val TODO_ID_KEY: String = "todo_id_key"
658 |
659 | fun newInstance(id: String): TodoEditFragment {
660 | var args: Bundle = Bundle()
661 | args.putString(TODO_ID_KEY, id)
662 | var todoEditFragment: TodoEditFragment = newInstance()
663 | todoEditFragment.arguments = args
664 | return todoEditFragment
665 | }
666 |
667 | fun newInstance(): TodoEditFragment {
668 | return TodoEditFragment()
669 | }
670 | }
671 |
672 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
673 | return UI {
674 | // AnkoContext
675 |
676 | verticalLayout {
677 | padding = dip(30)
678 | var title = editText {
679 | // editText 视图
680 | id = R.id.todo_title
681 | hintResource = R.string.title_hint
682 | }
683 |
684 | var content = editText {
685 | id = R.id.todo_content
686 | height = 400
687 | hintResource = R.string.content_hint
688 | }
689 | button {
690 | // button 视图
691 | id = R.id.todo_add
692 | textResource = R.string.add_todo
693 | textColor = Color.WHITE
694 | setBackgroundColor(Color.DKGRAY)
695 | onClick { _ -> createTodoFrom(title, content) }
696 | }
697 | }
698 | }.view
699 | }
700 |
701 | override fun onActivityCreated(savedInstanceState: Bundle?) {
702 | super.onActivityCreated(savedInstanceState)
703 |
704 | if (arguments != null && arguments.containsKey(TODO_ID_KEY)) {
705 |
706 | val todoId = arguments.getString(TODO_ID_KEY)
707 | todo = realm.where(Todo::class.java).equalTo("id", todoId).findFirst()
708 |
709 | val todoTitle = find(R.id.todo_title)
710 | todoTitle.setText(todo?.title)
711 |
712 | val todoContent = find(R.id.todo_content)
713 | todoContent.setText(todo?.content)
714 |
715 | val add = find