KMM- TODO 앱 만들기(2)(with sqlDelight)

WonDDak·2023년 4월 26일

KMM-TODO

목록 보기
2/2

설정

flow로 변경하기위해 다음 과정을 진행합니다.

coroutines 의존성 추가

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 확장 의존성이다.

Flow 추가

저번에 만들었던 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)

}

UI 변경

이제 flow를 적용시켜 봅시다.

Android

기존코드

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로 가져오니 상태가 변하면 자동으로 변경되니 신경쓸필요도 없구요

ios

기존코드

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 에서 확인할수 있습니다.~

profile
안녕하세요. 원딱입니다.

0개의 댓글