flow로 변경하기위해 다음 과정을 진행합니다.
shared 모듈의 gradle.kts에 다음과 같이 추가
kotlin {
sourceSets {
val sqlDelightVersion = "2.0.0-alpha05"
val commonMain by getting {
dependencies {
...
implementation("app.cash.sqldelight:coroutines-extensions:$sqlDelightVersion")
}
}
}
}
sqlDelight에서 지원하는 Coroutine 확장 의존성이다.
저번에 만들었던 AppDataBase class 아래 다음과 같이 변경합니다.
class AppDataBase(driverFactory: DriverFactory) {
private val driver = driverFactory.createDriver()
private val database = Database(driver)
private val queries = database.toDoItemQueries
fun insertItem(title: String) {
queries.insert(null, title, false)
}
fun deleteItem(id: Long) {
queries.deleteById(id)
}
fun updateCheck(checked: Boolean, id: Long) {
queries.updateFinish(checked, id)
}
fun getAllItemFlow() : Flow<List<TODOItem>> = queries.selectAll().asFlow().mapToList(Dispatchers.Main)
}
이제 flow를 적용시켜 봅시다.
class MainActivity : ComponentActivity() {
private lateinit var appDataBase: AppDataBase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//appDataBase 설정
appDataBase = AppDataBase(DriverFactory(this))
setContent {
MyApplicationTheme {
//처음 값 빈값
var itemList: List<TODOItem> by remember {
mutableStateOf(emptyList())
}
//시작 하면서 값 갱신
LaunchedEffect(true) {
itemList = appDataBase.getAllItems()
}
//scope 용
val scope = rememberCoroutineScope()
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
ToDoView(
itemList,
addAction = { title ->
scope.launch {
appDataBase.insertItem(title)
itemList = appDataBase.getAllItems()
}
},
deleteAction = { id ->
scope.launch {
appDataBase.deleteItem(id)
itemList = appDataBase.getAllItems()
}
},
checkToggle = { id, checked ->
scope.launch {
appDataBase.updateCheck(checked, id)
itemList = appDataBase.getAllItems()
}
}
)
}
}
}
}
}
class MainActivity : ComponentActivity() {
private lateinit var appDataBase: AppDataBase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//appDataBase 설정
appDataBase = AppDataBase(DriverFactory(this))
setContent {
MyApplicationTheme {
val todoItemList by appDataBase.getAllItemFlow().collectAsState(initial = emptyList())
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
ToDoView(
todoItemList,
addAction = { title ->
appDataBase.insertItem(title)
},
deleteAction = { id ->
appDataBase.deleteItem(id)
},
checkToggle = { id, checked ->
appDataBase.updateCheck(checked, id)
}
)
}
}
}
}
}
동작은 동일하고 코드가 더욱 깔끔해지는 효과가 있습니다.
그리고 flow로 가져오니 상태가 변하면 자동으로 변경되니 신경쓸필요도 없구요
struct ContentView: View {
let appDataBase : AppDataBase = AppDataBase(driverFactory: DriverFactory())
@State var itemList : [TODOItem] = []
@State var fieldText = ""
var body: some View {
VStack{
HStack {
TextField("enter TODO Title", text: $fieldText)
Spacer()
Button("ADD") {
appDataBase.insertItem(title: fieldText) { error in
updateItem(error: error) {
fieldText = ""
}
}
}
}.padding(10)
ForEach(itemList,id:\.self) { item in
ToDoRow(item: item) {
appDataBase.deleteItem(id: item.id) { error in
updateItem(error: error)
}
} updateToggle: {
appDataBase.updateCheck(checked: !item.isFinish, id: item.id) { error in
updateItem(error: error)
}
}
}
Spacer()
}.onAppear {
updateItem(error:nil)
}
}
func updateItem(error: Error?,otherAction :@escaping ()->Void = {}) {
if let error = error {
print(error)
} else {
appDataBase.getAllItems { list, error in
if let itemList = list {
self.itemList = itemList
otherAction()
}
}
}
}
}
struct ContentView: View {
let appDataBase : AppDataBase = AppDataBase(driverFactory: DriverFactory())
@State var itemList : [TODOItem] = []
@State var fieldText = ""
var body: some View {
VStack{
HStack {
TextField("enter TODO Title", text: $fieldText)
Spacer()
Button("ADD") {
appDataBase.insertItem(title: fieldText)
}
}.padding(10)
ForEach(itemList,id:\.self) { item in
ToDoRow(item: item) {
appDataBase.deleteItem(id: item.id)
} updateToggle: {
appDataBase.updateCheck(checked: !item.isFinish, id: item.id)
}
}
Spacer()
}.onAppear {
appDataBase.getAllItemFlow().collect(collector: Collector<[TODOItem]> {value in
self.itemList = value
}) { error in
print(error ?? "")
}
}
}
}
여기는 추가로 Kotlinx_coroutines_coreFlowCollector를 상속받는 class를 추가로 만들어 줍니다.
//CollecterHelper.swift
import Foundation
import shared
class Collector<T> : Kotlinx_coroutines_coreFlowCollector {
let callback:(T) -> Void
init(callback: @escaping (T) -> Void) {
self.callback = callback
}
func emit(value: Any?, completionHandler: @escaping (Error?) -> Void) {
callback(value as! T)
completionHandler(nil)
}
}
하나의 로직으로 android/ios에서 사용가능하것은 정말 좋은것 같습니다.
모든 코드는 https://github.com/jmseb3/KMM-TODO 에서 확인할수 있습니다.~