Skip to content

feat(core:domain): Migrate to KMP #2350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: kmp-impl
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ kotlin {

sourceSets {
commonMain.dependencies {
api(projects.core.model)

implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
api(libs.coil.kt)
Expand All @@ -52,6 +54,8 @@ kotlin {
api(libs.squareup.okio)
api(libs.jb.kotlin.stdlib)
api(libs.kotlinx.datetime)

implementation(libs.ktor.client.core)
}

androidMain.dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.common.utils

import com.mifos.core.model.objects.error.MifosError
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json

object MFErrorParser {
const val LOG_TAG: String = "MFErrorParser"

private val json: Json = Json { ignoreUnknownKeys = true }

private fun parseError(serverResponse: String?): MifosError {
return json.decodeFromString(serverResponse ?: "{}")
}

suspend fun errorMessage(throwableError: Throwable): String {
return try {
when (throwableError) {
is ClientRequestException, is ServerResponseException -> {
val response = (throwableError as? ClientRequestException)?.response
?: (throwableError as? ServerResponseException)?.response
val errorBody = response?.bodyAsText() ?: ""
parseError(errorBody).errors.firstOrNull()?.defaultUserMessage
?: "Something went wrong!"
}

else -> throwableError.message ?: "Unknown error"
}
} catch (exception: Exception) {
"Error processing response"
}
}
}
68 changes: 42 additions & 26 deletions core/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,53 @@
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.library)
alias(libs.plugins.mifos.android.library.jacoco)
alias(libs.plugins.mifos.android.koin)
// alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.mifos.kmp.library)
alias(libs.plugins.mifos.kmp.koin)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
Comment on lines +13 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this!?!

Copy link
Contributor Author

@biplab1 biplab1 Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are added for the following code in build.gradle of core:domain:

compose.resources {
    publicResClass = true
    generateResClass = always
    packageOfResClass = "core.domain.generated.resources"
}

alias(libs.plugins.kotlin.serialization)
}

android {
namespace = "com.mifos.core.domain"
}

dependencies {
api(projects.core.data)
api(projects.core.model)

implementation(libs.javax.inject)

implementation(libs.dbflow)

// sdk client
// implementation(libs.fineract.client)

implementation(libs.rxandroid)
implementation(libs.rxjava)

implementation(libs.squareup.okhttp)

implementation(libs.androidx.paging.runtime.ktx)

testImplementation(projects.core.testing)
testImplementation (libs.androidx.paging.common.ktx)
testImplementation (libs.androidx.paging.testing)
kotlin {
sourceSets {
commonMain.dependencies {
// api(projects.core.data)
api(projects.core.model)
api(projects.core.common)
api(projects.core.network)

implementation(libs.kotlinx.coroutines.core)
implementation(compose.components.resources)
implementation(libs.kotlinx.serialization.json)
// implementation(libs.fineract.client.kmp)
}

androidMain.dependencies {
implementation(libs.squareup.okhttp)
implementation(libs.ktor.client.okhttp)
implementation(libs.androidx.paging.runtime.ktx)
}
nativeMain.dependencies {
implementation(libs.ktor.client.darwin)
}
desktopMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
jsMain.dependencies {
implementation(libs.ktor.client.js)
}
wasmJsMain.dependencies {
implementation(libs.ktor.client.js)
}
}
}

implementation(libs.kotlinx.serialization.json)
compose.resources {
publicResClass = true
generateResClass = always
packageOfResClass = "core.domain.generated.resources"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Mifos Initiative
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
Expand All @@ -13,12 +13,15 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.mifos.core.data.repository.GroupsListRepository
import com.mifos.room.entities.group.GroupEntity
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.ServerResponseException
import java.io.IOException

class GroupsListPagingDataSource(
actual class GroupsListPagingDataSource(
private val repository: GroupsListRepository,
private val limit: Int,
) : PagingSource<Int, GroupEntity>() {

override fun getRefreshKey(state: PagingState<Int, GroupEntity>): Int? {
return state.anchorPosition?.let { position ->
state.closestPageToPosition(position)?.prevKey?.plus(limit)
Expand All @@ -38,6 +41,10 @@ class GroupsListPagingDataSource(
prevKey = if (currentOffset <= 0) null else currentOffset - limit,
nextKey = if (groups.isEmpty()) null else currentOffset + limit,
)
} catch (e: ClientRequestException) {
LoadResult.Error(e)
} catch (e: ServerResponseException) {
LoadResult.Error(e)
} catch (e: IOException) {
LoadResult.Error(e)
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.domain.useCases

import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File

actual class PlatformFile(private val pngFile: File) {
actual fun toMultipartData(): MultipartData {
val requestFile = pngFile.asRequestBody("image/png".toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("file", pngFile.name, requestFile)
return MultipartData(body)
}
}

actual class MultipartData(val body: MultipartBody.Part)
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ class ActivateCenterUseCase(
private val activateRepository: ActivateRepository,
) {

suspend operator fun invoke(
operator fun invoke(
centerId: Int,
centerPayload: ActivatePayload,
): Flow<Resource<PostCentersCenterIdResponse>> = flow {
try {
emit(Resource.Loading())
val response = activateRepository.activateCenter(centerId, centerPayload)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
}
emit(Resource.Loading())
val response = activateRepository.activateCenter(centerId, centerPayload)
emit(Resource.Success(response))
}.catch { exception ->
emit(Resource.Error(exception.message.toString()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ class ActivateClientUseCase(
private val activateRepository: ActivateRepository,
) {

suspend operator fun invoke(
operator fun invoke(
clientId: Int,
clientPayload: ActivatePayload,
): Flow<Resource<PostClientsClientIdResponse>> = flow {
try {
emit(Resource.Loading())
val response = activateRepository.activateClient(clientId, clientPayload)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
}
emit(Resource.Loading())
val response = activateRepository.activateClient(clientId, clientPayload)
emit(Resource.Success(response))
}.catch { exception ->
emit(Resource.Error(exception.message.toString()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.domain.useCases

import com.mifos.core.common.utils.MFErrorParser
import com.mifos.core.common.utils.Resource
import com.mifos.core.data.repository.ActivateRepository
import com.mifos.core.model.objects.clients.ActivatePayload
import com.mifos.core.network.GenericResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class ActivateGroupUseCase(
private val activateRepository: ActivateRepository,
) {

operator fun invoke(
groupId: Int,
groupPayload: ActivatePayload,
): Flow<Resource<GenericResponse>> = flow {
try {
emit(Resource.Loading())
val response = activateRepository.activateGroup(groupId, groupPayload)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(MFErrorParser.errorMessage(exception)))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,31 @@
*/
package com.mifos.core.domain.useCases

import com.mifos.core.common.utils.MFErrorParser
import com.mifos.core.common.utils.Resource
import com.mifos.core.data.repository.PinPointClientRepository
import com.mifos.core.data.repository.SavingsAccountActivateRepository
import com.mifos.core.network.GenericResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class DeleteClientAddressPinpointUseCase(
private val pinPointClientRepository: PinPointClientRepository,
/**
* Created by Pronay Sarker on 04/08/2024 (12:33 PM)
*/
class ActivateSavingsUseCase(
private val repository: SavingsAccountActivateRepository,
) {

suspend operator fun invoke(clientId: Int, addressId: Int): Flow<Resource<GenericResponse>> =
operator fun invoke(
savingsAccountId: Int,
request: HashMap<String, String>,
): Flow<Resource<GenericResponse>> =
flow {
try {
emit(Resource.Loading())
val response = pinPointClientRepository.deleteClientAddressPinpointLocation(
clientId,
addressId,
)
val response = repository.activateSavings(savingsAccountId, request)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
emit(Resource.Error(MFErrorParser.errorMessage(exception)))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,14 @@ class AddClientPinpointLocationUseCase(
private val pinPointClientRepository: PinPointClientRepository,
) {

suspend operator fun invoke(
operator fun invoke(
clientId: Int,
address: com.mifos.core.model.objects.clients.ClientAddressRequest,
): Flow<Resource<GenericResponse>> = flow {
try {
emit(Resource.Loading())
val response = pinPointClientRepository.addClientPinpointLocation(clientId, address)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
}
emit(Resource.Loading())
val response = pinPointClientRepository.addClientPinpointLocation(clientId, address)
emit(Resource.Success(response))
}.catch { exception ->
emit(Resource.Error(exception.message.toString()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ class AddDataTableEntryUseCase(
private val repository: DataTableRowDialogRepository,
) {

suspend operator fun invoke(
operator fun invoke(
table: String,
entityId: Int,
payload: Map<String, String>,
): Flow<Resource<GenericResponse>> = flow {
try {
emit(Resource.Loading())
val response = repository.addDataTableEntry(table, entityId, payload)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
}
emit(Resource.Loading())
val response = repository.addDataTableEntry(table, entityId, payload)
emit(Resource.Success(response))
}.catch { exception ->
emit(Resource.Error(exception.message.toString()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ class ApproveCheckerUseCase(
val repository: CheckerInboxRepository,
) {

suspend operator fun invoke(auditId: Int): Flow<Resource<GenericResponse>> = flow {
try {
emit(Resource.Loading())
val response = repository.approveCheckerEntry(auditId)
emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message.toString()))
}
operator fun invoke(auditId: Int): Flow<Resource<GenericResponse>> = flow {
emit(Resource.Loading())
val response = repository.approveCheckerEntry(auditId)
emit(Resource.Success(response))
}.catch { exception ->
emit(Resource.Error(exception.message.toString()))
}
}
Loading