Skip to main content

Core Module

The foundation layer with zero UI dependencies. Every other module in Miru SDK depends on :core.

implementation("com.github.wahidabd.miru-sdk:core:<version>")

Resource

Resource<T> is a sealed class that wraps all async operations into three states:

sealed class Resource<T> {
class Success<T>(val data: T) : Resource<T>()
class Error<T>(val exception: AppException, val data: T? = null) : Resource<T>()
class Loading<T>(val data: T? = null) : Resource<T>()
}

Callbacks

val result: Resource<User> = userApi.getUserById(1)

result
.onSuccess { user -> println(user.name) }
.onError { exception, data -> println(exception.message) }
.onLoading { println("Loading...") }

Transformations

// Transform the success data
val mapped: Resource<String> = result.map { it.name }

// Chain operations
val chained: Resource<Profile> = result.flatMap { user ->
profileApi.getProfile(user.id)
}

Data Retrieval

val user: User? = result.getOrNull()
val userSafe: User = result.getOrDefault(User.empty())
val userForced: User = result.getOrThrow()

Flow Conversion

// Convert Flow<T> to Flow<Resource<T>>
val resourceFlow: Flow<Resource<List<User>>> = userFlow.asResource()
info

See Resource API Reference for the complete API.

AppException

Typed exception hierarchy for structured error handling across layers:

sealed class AppException(message: String?) : Exception(message) {
class NetworkException(message: String?) : AppException(message)
class TimeoutException(message: String?) : AppException(message)
class UnauthorizedException(message: String?) : AppException(message)
class ForbiddenException(message: String?) : AppException(message)
class NotFoundException(message: String?) : AppException(message)
class ServerException(val code: Int, message: String?) : AppException(message)
class UnknownException(cause: Throwable?) : AppException(cause?.message)
}

Pattern match in your ViewModel or global error handler:

when (exception) {
is AppException.UnauthorizedException -> navigateToLogin()
is AppException.NetworkException -> showOfflineMessage()
is AppException.ServerException -> showServerError(exception.code)
is AppException.TimeoutException -> showRetryDialog()
else -> showGenericError()
}
tip

See Error Handling Guide for best practices.

Mapper

Type-safe mapping interface for DTO-to-domain conversions:

class UserMapper : Mapper<UserDto, User> {
override fun map(from: UserDto) = User(
id = from.id,
name = from.name,
email = from.email
)
}

Extensions

String Extensions

"hello world".capitalizeFirst()   // "Hello world"
"test@email.com".isValidEmail() // true

Flow Extensions

flow.throttleFirst(300L)
flow.retryWithExponentialBackoff(maxRetries = 3)
flow.asResource() // Flow<T> → Flow<Resource<T>>

Collection Extensions

list.safeGet(99) // null instead of IndexOutOfBoundsException
list.updateIf({ it.id == 5 }) { it.copy(name = "Updated") }

DateTime Extensions

Kotlinx DateTime helpers for common date/time operations.

Logger

Napier-based multiplatform logging:

MiruLogger.d("Debug message")
MiruLogger.e("Error message", throwable)