From 51173bdfeeac101b32db2254472dc39a1bcb0777 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 16 Sep 2020 20:36:34 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Room=20=E7=9B=B8?= =?UTF-8?q?=E9=97=9C=E7=9A=84=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 4c56948..f12fb9a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: "kotlin-kapt" apply plugin: "androidx.navigation.safeargs.kotlin" android { @@ -51,4 +52,16 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + /*** Room Dependencies Start ***/ + def room_version = "2.2.5" + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + + // optional - Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" + + // optional - Test helpers + testImplementation "androidx.room:room-testing:$room_version" + /*** Room Dependencies End ***/ } \ No newline at end of file From ffbaea18006460e0b42d1ece44afd337b0ef8c21 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Tue, 22 Sep 2020 21:20:25 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E7=9A=84=20DB=20Class?= =?UTF-8?q?=20=E5=BB=BA=E7=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/AppDatabase.kt | 34 ++++++++++++++++ .../database/GenericDao.kt | 40 +++++++++++++++++++ .../database/TodoItem.kt | 26 ++++++++++++ .../database/TodoItemDao.kt | 10 +++++ 4 files changed, 110 insertions(+) create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItem.kt create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt new file mode 100644 index 0000000..cf071fb --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt @@ -0,0 +1,34 @@ +package tw.andyang.kotlinandroidworkshop.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database( + version = 1, + entities = [ + TodoItem::class + ], + exportSchema = false +) +abstract class AppDatabase: RoomDatabase() { + companion object { + private const val DATABASE_NAME = "todo_list_db" + + // For Singleton instantiation + @Volatile private var instance: AppDatabase? = null + + fun getInstance(context: Context): AppDatabase { + return instance ?: synchronized(this) { + instance ?: buildDatabase(context).also { instance = it } + } + } + + private fun buildDatabase(context: Context): AppDatabase { + return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME).build() + } + } + + abstract fun todoItemDao(): TodoItemDao +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt new file mode 100644 index 0000000..85a2228 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt @@ -0,0 +1,40 @@ +package tw.andyang.kotlinandroidworkshop.database + +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Update + +interface GenericDao { + + /** + * Insert an object in the database. + * + * @param obj the object to be inserted. + */ + @Insert + fun insert(obj: T) + + /** + * Insert an array of objects in the database. + * + * @param obj the objects to be inserted. + */ + @Insert + fun insert(vararg obj: T) + + /** + * Update an object from the database. + * + * @param obj the object to be updated + */ + @Update + fun update(obj: T) + + /** + * Delete an object from the database + * + * @param obj the object to be deleted + */ + @Delete + fun delete(obj: T) +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItem.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItem.kt new file mode 100644 index 0000000..2c5f505 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItem.kt @@ -0,0 +1,26 @@ +package tw.andyang.kotlinandroidworkshop.database + +import androidx.room.* +import java.util.* + +@Entity( + tableName = TodoItem.TABLE_NAME +) +data class TodoItem ( + @ColumnInfo(name = COLUMN_TITLE) var title: String, + @ColumnInfo(name = COLUMN_DONE) var done: Boolean, + @ColumnInfo(name = COLUMN_CREATED_AT) var createdAt: Date +) { + companion object { + const val TABLE_NAME = "todo_items" + + const val COLUMN_ID = "id" + const val COLUMN_TITLE = "title" + const val COLUMN_DONE = "done" + const val COLUMN_CREATED_AT = "created_at" + } + + // 必須為 var 才會有 setter + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = COLUMN_ID) var id: Int = 0 +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt new file mode 100644 index 0000000..f6e5c1e --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt @@ -0,0 +1,10 @@ +package tw.andyang.kotlinandroidworkshop.database + +import androidx.room.Query + +@Dao +interface TodoItemDao: GenericDao { + + @Query("SELECT * FROM ${TodoItem.TABLE_NAME} ORDER BY ${TodoItem.COLUMN_CREATED_AT} DESC") + fun findAll(): List +} \ No newline at end of file From 2f84cc57dd7c028d39b1511bf1ca944356f1c9fa Mon Sep 17 00:00:00 2001 From: Cateyes Date: Tue, 22 Sep 2020 23:50:42 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Converter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/AppDatabase.kt | 2 ++ .../kotlinandroidworkshop/database/Converters.kt | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/database/Converters.kt diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt index cf071fb..73b73bf 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/AppDatabase.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.TypeConverters @Database( version = 1, @@ -12,6 +13,7 @@ import androidx.room.RoomDatabase ], exportSchema = false ) +@TypeConverters(Converters::class) abstract class AppDatabase: RoomDatabase() { companion object { private const val DATABASE_NAME = "todo_list_db" diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/Converters.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/Converters.kt new file mode 100644 index 0000000..c469256 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/Converters.kt @@ -0,0 +1,16 @@ +package tw.andyang.kotlinandroidworkshop.database + +import androidx.room.TypeConverter +import java.util.* + +class Converters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time + } +} From d246cf96d6e4f5c0ef2a3d78d5542488c51e10c0 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 00:06:36 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E8=99=95=E7=90=86=20MVVM=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=20DI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../andyang/kotlinandroidworkshop/AddTodoFragment.kt | 10 +++++++++- .../kotlinandroidworkshop/TodoListFragment.kt | 10 +++++++++- .../mvvm/AnyViewModelFactory.kt | 12 ++++++++++++ .../repository/TodoItemRepository.kt | 10 ++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/mvvm/AnyViewModelFactory.kt create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt index 0be5ef1..2d2925a 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt @@ -11,6 +11,9 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import kotlinx.android.synthetic.main.fragment_add_todo.* +import tw.andyang.kotlinandroidworkshop.database.AppDatabase +import tw.andyang.kotlinandroidworkshop.mvvm.AnyViewModelFactory +import tw.andyang.kotlinandroidworkshop.repository.TodoItemRepository class AddTodoFragment : Fragment() { @@ -40,7 +43,12 @@ class AddTodoFragment : Fragment() { editTodo.setText(args.memo) editTodo.setSelection(args.memo.length) - val todoViewModel = ViewModelProvider(requireActivity()).get(TodoViewModel::class.java) + val todoItemDb = AppDatabase.getInstance(requireActivity().applicationContext) + val todoItemRepo = TodoItemRepository(todoItemDb) + val viewModelFactory = AnyViewModelFactory { + TodoViewModel(todoItemRepo) + } + val todoViewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(TodoViewModel::class.java) buttonAdd.setOnClickListener { if (editTodo.text.isNullOrEmpty()) { diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt index 0a6c152..5c1cffe 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt @@ -11,6 +11,9 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.fragment_todo_list.* +import tw.andyang.kotlinandroidworkshop.database.AppDatabase +import tw.andyang.kotlinandroidworkshop.mvvm.AnyViewModelFactory +import tw.andyang.kotlinandroidworkshop.repository.TodoItemRepository class TodoListFragment : Fragment() { @@ -36,7 +39,12 @@ class TodoListFragment : Fragment() { ) ) - val todoViewModel = ViewModelProvider(requireActivity()).get(TodoViewModel::class.java) + val todoItemDb = AppDatabase.getInstance(requireActivity().applicationContext) + val todoItemRepo = TodoItemRepository(todoItemDb) + val viewModelFactory = AnyViewModelFactory { + TodoViewModel(todoItemRepo) + } + val todoViewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(TodoViewModel::class.java) todoViewModel.todoLiveData.observe(viewLifecycleOwner, Observer { todos: List -> adapter.submitList(todos) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/mvvm/AnyViewModelFactory.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/mvvm/AnyViewModelFactory.kt new file mode 100644 index 0000000..26b821e --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/mvvm/AnyViewModelFactory.kt @@ -0,0 +1,12 @@ +package tw.andyang.kotlinandroidworkshop.mvvm + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class AnyViewModelFactory(val creator: () -> T) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + return creator() as T + } + +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt new file mode 100644 index 0000000..0427b1f --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt @@ -0,0 +1,10 @@ +package tw.andyang.kotlinandroidworkshop.repository + +import androidx.lifecycle.LiveData +import tw.andyang.kotlinandroidworkshop.database.AppDatabase +import tw.andyang.kotlinandroidworkshop.database.TodoItem + +class TodoItemRepository( + private val database: AppDatabase +) { +} \ No newline at end of file From 2c7abc319ea209c4c7100d0295b4947c8455721b Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 00:18:48 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=9A=84=E7=A8=8B=E5=BC=8F=E7=A2=BC=EF=BC=8C=E6=AD=A4=E6=99=82?= =?UTF-8?q?=E6=9C=83=20crash=E3=80=82=E5=9B=A0=E7=82=BA=E5=9C=A8=20UI=20Th?= =?UTF-8?q?read=20=E5=9F=B7=E8=A1=8C=20DB=20=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlinandroidworkshop/AddTodoFragment.kt | 3 ++- .../andyang/kotlinandroidworkshop/TodoViewModel.kt | 13 ++++++++++++- .../repository/TodoItemRepository.kt | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt index 2d2925a..b4a0592 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/AddTodoFragment.kt @@ -57,7 +57,8 @@ class AddTodoFragment : Fragment() { // clear error editTodo.error = null // post data to view model - todoViewModel.onNewTodo.postValue(editTodo.text.toString()) + val title = editTodo.text.toString() + todoViewModel.createNewTodo(title) // hide soft keyboard when item added view.clearFocus() inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt index 4386393..7942761 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt @@ -4,8 +4,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import tw.andyang.kotlinandroidworkshop.database.TodoItem +import tw.andyang.kotlinandroidworkshop.repository.TodoItemRepository +import java.util.* -class TodoViewModel : ViewModel() { +class TodoViewModel(private val repository: TodoItemRepository) : ViewModel() { val onNewTodo = MutableLiveData() @@ -15,5 +18,13 @@ class TodoViewModel : ViewModel() { this.value = this.value!! + listOf(todo) } value = mutableListOf(Todo.Title("This is a title")) + + fun createNewTodo(title: String) { + val todoItem = TodoItem( + title = title, + done = false, + createdAt = Date() + ) + repository.insertTodoItem(todoItem) } } \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt index 0427b1f..37639ed 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt @@ -7,4 +7,7 @@ import tw.andyang.kotlinandroidworkshop.database.TodoItem class TodoItemRepository( private val database: AppDatabase ) { + fun insertTodoItem(todoItem: TodoItem) { + database.todoItemDao().insert(todoItem) + } } \ No newline at end of file From 0a97c38b81e54bf67c40ea3839fc0b689577b888 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 00:23:39 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E5=B0=87=E6=96=B0=E5=A2=9E=E6=94=B9?= =?UTF-8?q?=E7=82=BA=E4=BD=BF=E7=94=A8=20Coroutine=20=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E9=95=B7=E6=99=82=E9=96=93=E4=BB=BB?= =?UTF-8?q?=E5=8B=99=E9=98=BB=E7=A4=99=E6=9B=B4=E6=96=B0=20UI=20Thread=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tw/andyang/kotlinandroidworkshop/TodoViewModel.kt | 5 ++++- .../andyang/kotlinandroidworkshop/database/GenericDao.kt | 8 ++++---- .../repository/TodoItemRepository.kt | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt index 7942761..a58df97 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import kotlinx.coroutines.launch import tw.andyang.kotlinandroidworkshop.database.TodoItem import tw.andyang.kotlinandroidworkshop.repository.TodoItemRepository import java.util.* @@ -25,6 +26,8 @@ class TodoViewModel(private val repository: TodoItemRepository) : ViewModel() { done = false, createdAt = Date() ) - repository.insertTodoItem(todoItem) + viewModelScope.launch { + repository.insertTodoItem(todoItem) + } } } \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt index 85a2228..92e3c3c 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/GenericDao.kt @@ -12,7 +12,7 @@ interface GenericDao { * @param obj the object to be inserted. */ @Insert - fun insert(obj: T) + suspend fun insert(obj: T) /** * Insert an array of objects in the database. @@ -20,7 +20,7 @@ interface GenericDao { * @param obj the objects to be inserted. */ @Insert - fun insert(vararg obj: T) + suspend fun insert(vararg obj: T) /** * Update an object from the database. @@ -28,7 +28,7 @@ interface GenericDao { * @param obj the object to be updated */ @Update - fun update(obj: T) + suspend fun update(obj: T) /** * Delete an object from the database @@ -36,5 +36,5 @@ interface GenericDao { * @param obj the object to be deleted */ @Delete - fun delete(obj: T) + suspend fun delete(obj: T) } \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt index 37639ed..01026c6 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt @@ -7,7 +7,7 @@ import tw.andyang.kotlinandroidworkshop.database.TodoItem class TodoItemRepository( private val database: AppDatabase ) { - fun insertTodoItem(todoItem: TodoItem) { + suspend fun insertTodoItem(todoItem: TodoItem) { database.todoItemDao().insert(todoItem) } } \ No newline at end of file From 511f014f5160075bca39411bdc1ed9c892b83e1f Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 00:27:48 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=8E=A5=E4=B8=8A=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E8=B3=87=E6=96=99=EF=BC=8C=E8=BD=89=E7=82=BA=20LiveData=20?= =?UTF-8?q?=E5=8E=BB=E6=9B=B4=E6=96=B0=E7=95=AB=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tw/andyang/kotlinandroidworkshop/Todo.kt | 6 ++++- .../kotlinandroidworkshop/TodoViewModel.kt | 25 ++++++++++++------- .../database/TodoItemDao.kt | 4 ++- .../repository/TodoItemRepository.kt | 4 +++ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/Todo.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/Todo.kt index d3a3984..1adb5a4 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/Todo.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/Todo.kt @@ -1,10 +1,14 @@ package tw.andyang.kotlinandroidworkshop +import java.util.* + sealed class Todo(val viewType: Int) { data class Title(val text: String) : Todo(TYPE_TITLE) data class Item( + val id: Int, val memo: String, - val checked: Boolean + val checked: Boolean, + val createdAt: Date ) : Todo(TYPE_ITEM) companion object { diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt index a58df97..cefb721 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt @@ -1,9 +1,6 @@ package tw.andyang.kotlinandroidworkshop -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel +import androidx.lifecycle.* import kotlinx.coroutines.launch import tw.andyang.kotlinandroidworkshop.database.TodoItem import tw.andyang.kotlinandroidworkshop.repository.TodoItemRepository @@ -11,14 +8,24 @@ import java.util.* class TodoViewModel(private val repository: TodoItemRepository) : ViewModel() { - val onNewTodo = MutableLiveData() + private val title = Todo.Title("This is a title") val todoLiveData: LiveData> = MediatorLiveData>().apply { - addSource(onNewTodo) { text -> - val todo = Todo.Item(text, false) - this.value = this.value!! + listOf(todo) + val source = repository.getTodoItems().map { + it.map { todoItem -> + Todo.Item( + todoItem.id, + todoItem.title, + todoItem.done, + todoItem.createdAt + ) + } } - value = mutableListOf(Todo.Title("This is a title")) + addSource(source) { + this.value = mutableListOf(title) + it + } + value = mutableListOf(title) + } fun createNewTodo(title: String) { val todoItem = TodoItem( diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt index f6e5c1e..9ac617a 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/database/TodoItemDao.kt @@ -1,10 +1,12 @@ package tw.andyang.kotlinandroidworkshop.database +import androidx.lifecycle.LiveData +import androidx.room.Dao import androidx.room.Query @Dao interface TodoItemDao: GenericDao { @Query("SELECT * FROM ${TodoItem.TABLE_NAME} ORDER BY ${TodoItem.COLUMN_CREATED_AT} DESC") - fun findAll(): List + fun findAll(): LiveData> } \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt index 01026c6..d810f83 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt @@ -10,4 +10,8 @@ class TodoItemRepository( suspend fun insertTodoItem(todoItem: TodoItem) { database.todoItemDao().insert(todoItem) } + + fun getTodoItems(): LiveData> { + return database.todoItemDao().findAll() + } } \ No newline at end of file From ba27f8ed88b40c2330b481836e58dc93be645120 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 01:03:35 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E8=AE=8A=E6=9B=B4?= =?UTF-8?q?=E5=84=B2=E5=AD=98=E5=8B=BE=E9=81=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 要用 setOnClickListener,不要用 setOnCheckedChangeListener 不然會觸發兩次 --- .../OnTodoChangeListener.kt | 5 +++++ .../kotlinandroidworkshop/TodoAdapter.kt | 9 ++++++-- .../kotlinandroidworkshop/TodoListFragment.kt | 22 ++++++++++++------- .../kotlinandroidworkshop/TodoViewModel.kt | 12 ++++++++++ .../repository/TodoItemRepository.kt | 4 ++++ 5 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/tw/andyang/kotlinandroidworkshop/OnTodoChangeListener.kt diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/OnTodoChangeListener.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/OnTodoChangeListener.kt new file mode 100644 index 0000000..821ce17 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/OnTodoChangeListener.kt @@ -0,0 +1,5 @@ +package tw.andyang.kotlinandroidworkshop + +interface OnTodoChangeListener { + fun onChange(todo: Todo.Item) +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt index 928f01c..49bcda4 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt @@ -21,6 +21,8 @@ class TodoAdapter : ListAdapter( } ) { + var onTodoChangeListener: OnTodoChangeListener? = null + override fun getItemViewType(position: Int): Int { return getItem(position).viewType } @@ -28,7 +30,7 @@ class TodoAdapter : ListAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { Todo.TYPE_TITLE -> TodoTitleViewHolder(parent) - else -> TodoViewHolder(parent) + else -> TodoViewHolder(parent, onTodoChangeListener) } } @@ -40,7 +42,7 @@ class TodoAdapter : ListAdapter( } } -class TodoViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( +class TodoViewHolder(parent: ViewGroup, private val onTodoChangeListener: OnTodoChangeListener?) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false) ) { @@ -49,6 +51,9 @@ class TodoViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( fun bind(todo: Todo.Item) { checkbox.text = todo.memo checkbox.isChecked = todo.checked + checkbox.setOnClickListener { view -> + onTodoChangeListener?.onChange(Todo.Item(todo.id, todo.memo, !todo.checked, todo.createdAt)) + } } } diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt index 5c1cffe..a6a429d 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoListFragment.kt @@ -28,7 +28,20 @@ class TodoListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val adapter = TodoAdapter() + val todoItemDb = AppDatabase.getInstance(requireActivity().applicationContext) + val todoItemRepo = TodoItemRepository(todoItemDb) + val viewModelFactory = AnyViewModelFactory { + TodoViewModel(todoItemRepo) + } + val todoViewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(TodoViewModel::class.java) + + val adapter = TodoAdapter().apply { + onTodoChangeListener = object : OnTodoChangeListener { + override fun onChange(todo: Todo.Item) { + todoViewModel.updateTodo(todo) + } + } + } recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) @@ -39,13 +52,6 @@ class TodoListFragment : Fragment() { ) ) - val todoItemDb = AppDatabase.getInstance(requireActivity().applicationContext) - val todoItemRepo = TodoItemRepository(todoItemDb) - val viewModelFactory = AnyViewModelFactory { - TodoViewModel(todoItemRepo) - } - val todoViewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(TodoViewModel::class.java) - todoViewModel.todoLiveData.observe(viewLifecycleOwner, Observer { todos: List -> adapter.submitList(todos) }) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt index cefb721..f10b239 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoViewModel.kt @@ -37,4 +37,16 @@ class TodoViewModel(private val repository: TodoItemRepository) : ViewModel() { repository.insertTodoItem(todoItem) } } + + fun updateTodo(todo: Todo.Item) { + val todoItem = TodoItem( + title = todo.memo, + done = todo.checked, + createdAt = todo.createdAt + ).apply { id = todo.id } + + viewModelScope.launch { + repository.updateTodoItem(todoItem) + } + } } \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt index d810f83..a39a282 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/TodoItemRepository.kt @@ -11,6 +11,10 @@ class TodoItemRepository( database.todoItemDao().insert(todoItem) } + suspend fun updateTodoItem(todoItem: TodoItem) { + database.todoItemDao().update(todoItem) + } + fun getTodoItems(): LiveData> { return database.todoItemDao().findAll() } From c93b1ed7e38a17dc1e12f00b1d523574ce75eae6 Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 01:36:31 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E9=A1=AF=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt | 4 ++++ app/src/main/res/layout/item_todo.xml | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt index 49bcda4..610e683 100644 --- a/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/TodoAdapter.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.item_todo.view.* +import java.text.SimpleDateFormat class TodoAdapter : ListAdapter( object : DiffUtil.ItemCallback() { @@ -47,6 +48,7 @@ class TodoViewHolder(parent: ViewGroup, private val onTodoChangeListener: OnTodo ) { private val checkbox: AppCompatCheckBox = itemView.checkbox + private val date = itemView.create_at fun bind(todo: Todo.Item) { checkbox.text = todo.memo @@ -54,6 +56,8 @@ class TodoViewHolder(parent: ViewGroup, private val onTodoChangeListener: OnTodo checkbox.setOnClickListener { view -> onTodoChangeListener?.onChange(Todo.Item(todo.id, todo.memo, !todo.checked, todo.createdAt)) } + + date.text = SimpleDateFormat.getDateTimeInstance().format(todo.createdAt) } } diff --git a/app/src/main/res/layout/item_todo.xml b/app/src/main/res/layout/item_todo.xml index fe33dd8..526a52e 100644 --- a/app/src/main/res/layout/item_todo.xml +++ b/app/src/main/res/layout/item_todo.xml @@ -8,8 +8,14 @@ + + \ No newline at end of file From 6932fe443af1392e4a37b1cf8cd8264d39f2eeac Mon Sep 17 00:00:00 2001 From: Cateyes Date: Wed, 23 Sep 2020 01:36:48 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E5=90=8C=E5=A0=B4=E5=8A=A0=E6=98=A0=20IS?= =?UTF-8?q?O=208601=20=E6=97=A5=E6=9C=9F=E6=A0=BC=E5=BC=8F=E6=AD=A3?= =?UTF-8?q?=E7=A2=BA=E8=99=95=E7=90=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlinandroidworkshop/DateTimeTest.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/test/java/tw/andyang/kotlinandroidworkshop/DateTimeTest.kt diff --git a/app/src/test/java/tw/andyang/kotlinandroidworkshop/DateTimeTest.kt b/app/src/test/java/tw/andyang/kotlinandroidworkshop/DateTimeTest.kt new file mode 100644 index 0000000..532cadb --- /dev/null +++ b/app/src/test/java/tw/andyang/kotlinandroidworkshop/DateTimeTest.kt @@ -0,0 +1,20 @@ +package tw.andyang.kotlinandroidworkshop + +import org.junit.Test +import java.text.SimpleDateFormat +import java.util.* + +class DateTimeTest { + @Test + fun testDateTime() { + val iso8601DatetimeString = "2020-09-21T04:34:56.789Z" + // 上面這個 Z 這是 UTC+0 時區!不要把這個 Z 當一般文字處理! + // 拜託不要再用 yyyy-MM-dd'T'HH:mm:ss.SSS'Z' + val date = SimpleDateFormat("y-M-d'T'H:m:s.SSSX").parse(iso8601DatetimeString) + val localTimeString = SimpleDateFormat("yyyy-MM-dd HH:mm:ssX").apply { + timeZone = TimeZone.getDefault() + }.format(date) + + println(localTimeString) + } +} \ No newline at end of file