
Lightweight, flexible network library offering a clean, intuitive API for handling network requests with support for LiveData, Flow, object deserialization, customizable headers, and local data integration.
A lightweight, multiplatform network library for Kotlin β seamless API calls with Flow, LiveData, and native performance.
Managing network calls, caching, and syncing with local databases can become a repetitive mess in every project.
NetFlow KMP was built to solve these common problems:
β All of this with just a few lines of Kotlin code β no manual list mutations, no state juggling, and no complicated observer logic.
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.kmpbits:netflow-core:<latest_version>")
}Check the latest version on Maven Central
val client = netFlowClient {
baseUrl = "https://api.example.com"
// Optional default headers
header(Header(HttpHeader.custom("custom-header"), "This is a custom header"))
header(Header(HttpHeader.CONTENT_TYPE), "application/json")
}val response = client.call {
path = "/users"
method = HttpMethod.Get
}.response()val user: User = client.call {
path = "/users/1"
}.responseToModel<User>()val userFlow = client.call {
path = "/users/1"
}.responseFlow<User>()Customize the flow behavior:
val usersFlow = client.call {
path = "/users"
method = HttpMethod.Get
}.responseFlow<List<User>> {
onNetworkSuccess { usersDto ->
// Convert DTO to Entity table
userDao.insertAll(users.map(UserDto::toEntity))
}
local({
observe {
userDao.getAllUsers()
}
// Convert Entity to DTO, if the database object is different than the network
// because the return type from your database must match the network DTO
}, transform = { it.map(UserEntity::ToDto) })
}.map {
// Convert all of the response to models as it is the return type of the function
it.map { it.map(UserDto::toModel) }
}UserDto).transform parameter inside local() to convert to the DTO type,
otherwise, you will get a ClassCastException.onlyLocalCall = true inside the local DSL block.local({
onlyLocalCall = true
call {
userDao.getAllUsers()
}
}, transform = { it.map { dto -> dto.toModel() } })All the responses can be mapped at once using the map extension inside ResultState:
.map {
it.map { dtoList -> dtoList.map { it.toModel() } }
}Using lifecycle:
userFlow.observe(viewLifecycleOwner) { state ->
when(state) {
is ResultState.Loading -> showLoading()
is ResultState.Success -> showUsers(state.data)
is ResultState.Error -> showError(state.exception.message)
is ResultState.Empty -> showEmptyState()
}
}Or with coroutines:
lifecycleScope.launch {
userFlow.collectLatest { state ->
// same logic as above
}
}NetFlow also supports suspending requests for simpler APIs where observation is not required.
suspend fun deleteUser(id: Int): AsyncState<User> {
return client.call {
path = "users/$id"
method = HttpMethod.Delete
}.responseAsync<UserDto> {
onNetworkSuccess {
userDao.deleteUser(id)
}
}.map(UserDto::toModel)
}client.call {
path = "/secure-endpoint"
header(Header(HttpHeader.custom("custom-header"), "This is a custom header"))
header(Header(HttpHeader.CONTENT_TYPE), "application/json")
}.responseFlow<SecureData>()client.call {
path = "/users"
parameter("role" to "admin")
parameter("active" to true)
}.responseFlow<List<User>>()responseToModel is the only extension that needs to be used with try catch.
try {
val response = client.call {
path = "/might-fail"
}.responseToModel<Data>()
} catch (e: StateTalkException) {
when (e) {
is NetworkException -> { /* handle network issues */ }
is SerializationException -> { /* handle parsing errors */ }
is HttpException -> {
val code = e.code
val errorBody = e.errorBody
}
}
}single {
netFlowClient {
baseUrl = "https://api.example.com"
}
}Will be implemented soon.
This project is licensed under the MIT License.
A lightweight, multiplatform network library for Kotlin β seamless API calls with Flow, LiveData, and native performance.
Managing network calls, caching, and syncing with local databases can become a repetitive mess in every project.
NetFlow KMP was built to solve these common problems:
β All of this with just a few lines of Kotlin code β no manual list mutations, no state juggling, and no complicated observer logic.
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.kmpbits:netflow-core:<latest_version>")
}Check the latest version on Maven Central
val client = netFlowClient {
baseUrl = "https://api.example.com"
// Optional default headers
header(Header(HttpHeader.custom("custom-header"), "This is a custom header"))
header(Header(HttpHeader.CONTENT_TYPE), "application/json")
}val response = client.call {
path = "/users"
method = HttpMethod.Get
}.response()val user: User = client.call {
path = "/users/1"
}.responseToModel<User>()val userFlow = client.call {
path = "/users/1"
}.responseFlow<User>()Customize the flow behavior:
val usersFlow = client.call {
path = "/users"
method = HttpMethod.Get
}.responseFlow<List<User>> {
onNetworkSuccess { usersDto ->
// Convert DTO to Entity table
userDao.insertAll(users.map(UserDto::toEntity))
}
local({
observe {
userDao.getAllUsers()
}
// Convert Entity to DTO, if the database object is different than the network
// because the return type from your database must match the network DTO
}, transform = { it.map(UserEntity::ToDto) })
}.map {
// Convert all of the response to models as it is the return type of the function
it.map { it.map(UserDto::toModel) }
}UserDto).transform parameter inside local() to convert to the DTO type,
otherwise, you will get a ClassCastException.onlyLocalCall = true inside the local DSL block.local({
onlyLocalCall = true
call {
userDao.getAllUsers()
}
}, transform = { it.map { dto -> dto.toModel() } })All the responses can be mapped at once using the map extension inside ResultState:
.map {
it.map { dtoList -> dtoList.map { it.toModel() } }
}Using lifecycle:
userFlow.observe(viewLifecycleOwner) { state ->
when(state) {
is ResultState.Loading -> showLoading()
is ResultState.Success -> showUsers(state.data)
is ResultState.Error -> showError(state.exception.message)
is ResultState.Empty -> showEmptyState()
}
}Or with coroutines:
lifecycleScope.launch {
userFlow.collectLatest { state ->
// same logic as above
}
}NetFlow also supports suspending requests for simpler APIs where observation is not required.
suspend fun deleteUser(id: Int): AsyncState<User> {
return client.call {
path = "users/$id"
method = HttpMethod.Delete
}.responseAsync<UserDto> {
onNetworkSuccess {
userDao.deleteUser(id)
}
}.map(UserDto::toModel)
}client.call {
path = "/secure-endpoint"
header(Header(HttpHeader.custom("custom-header"), "This is a custom header"))
header(Header(HttpHeader.CONTENT_TYPE), "application/json")
}.responseFlow<SecureData>()client.call {
path = "/users"
parameter("role" to "admin")
parameter("active" to true)
}.responseFlow<List<User>>()responseToModel is the only extension that needs to be used with try catch.
try {
val response = client.call {
path = "/might-fail"
}.responseToModel<Data>()
} catch (e: StateTalkException) {
when (e) {
is NetworkException -> { /* handle network issues */ }
is SerializationException -> { /* handle parsing errors */ }
is HttpException -> {
val code = e.code
val errorBody = e.errorBody
}
}
}single {
netFlowClient {
baseUrl = "https://api.example.com"
}
}Will be implemented soon.
This project is licensed under the MIT License.