How to create a callback function in Kotlin? - kotlin

I'm having trouble creating a callback function with following scenario:
A kafka consumer listens to new messages to be able to record in the database:
suspend fun consumerClient(service: ClientService) {
val messages = consumerCommands(
"create-client", "localhost:9092", "consumer-client", false,
OffsetBehaviour.Earliest, 10
)
}
suspend fun consumerCommands(
topic: String,
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int
) {
val consumer = consumer(bootstrapServers, group, autoCommit, offsetBehaviour, pollMax)
consumer.subscribe(mutableListOf(topic))
while (true) {
val records = consumer.poll(Duration.ofMillis(1000))
if (records.count() > 0) {
records.forEach {
val entity = treeToValue(it.value().get("message"), Client::class.java) as Client
ClientService().insert(entity)
}
}
}
}
It works perfectly. But I'm trying to create something more generic as follows:
interface KafkaConsumer<T> {
fun execute(callback: (T) -> Unit)
}
suspend fun <T> consumerCommand(
topic: String,
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int,
callback: KafkaConsumer<T>
): ConsumerRecords<String, JsonNode>? {
val consumer = consumer(bootstrapServers, group, autoCommit, offsetBehaviour, pollMax)
consumer.subscribe(mutableListOf(topic))
while (true) {
val records = consumer.poll(Duration.ofMillis(1000))
if (records.count() > 0) {
records.forEach {
val entity = (treeToValue(it.value().get("message"), Any::class.java) as T)
coroutineScope {
callback.execute { entity }
}
}
}
}
}
suspend fun consumerClient(service: ClientService) {
val messages = consumerCommand<Client>(
"create-client", "localhost:9092", "consumer-client", false,
OffsetBehaviour.Earliest, 10, {client: Client -> ClientService().insert(client)}
)
}
But it is not working. Could anyone help?

Maybe you can do something like this:
suspend fun <T> consumerCommand(
topic: String,
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int,
callback: (entity: T) -> Unit>
): ConsumerRecords<String, JsonNode>? {
val consumer = consumer(bootstrapServers, group, autoCommit, offsetBehaviour, pollMax)
consumer.subscribe(mutableListOf(topic))
while (true) {
val records = consumer.poll(Duration.ofMillis(1000))
if (records.count() > 0) {
records.forEach {
val entity = (treeToValue(it.value().get("message"), Any::class.java) as T)
callback(entity)
}
}
}
}
suspend fun consumerClient(service: ClientService) {
val messages = consumerCommand<Client>(
"create-client", "localhost:9092", "consumer-client", false,
OffsetBehaviour.Earliest, 10, {client: Client -> ClientService().insert(client)}
)
}

You are trying to use a lambda { client: Client -> ... } where a KafkaConsumer is expected. Instead you need to use a function type. An equivalent of your KafkaConsumer would be ((T) -> Unit) -> Unit, but I suspect it's an error and you actually want
suspend fun <T> consumerCommand(
topic: String,
bootstrapServers: String,
group: String,
autoCommit: Boolean,
offsetBehaviour: OffsetBehaviour,
pollMax: Int,
callback: (T) -> Unit
): ConsumerRecords<String, JsonNode>? {
...
coroutineScope {
callback(entity)
}
}
}
}
}
Or even suspend (T) -> Unit.
A side note: you don't use service argument in your consumerClient function, but instead create a new ClientService; is that intentional?

Related

How to pass extension function as argument?

There is an extension functions
fun List<Track>.sortByTitle(): List<Track> {
return this.sortedBy { it.title }
}
fun List<Track>.sortByArtist(): List<Track> {
return this.sortedBy { it.artists[0].name }
}
What need to write in arguments of sort function in order to pass an extension function?
fun sortByTitle(owner: String, kind: String) {
sort(owner, kind) // pass List<Track>.sortByTitle
}
fun sortByArtist(owner: String, kind: String) {
sort(owner, kind) // pass List<Track>.sortByArtist
}
private fun sort(owner: String, kind: String) {
val remote = playlist.get(owner, kind)
val tracks = // call extension function: remote.tracks.sortByTitle()
// ...
}
We can pass functions using function types, for example:
fun sortByTitle(owner: String, kind: String) {
sort(owner, kind, List<Track>::sortByTitle)
}
fun sortByArtist(owner: String, kind: String) {
sort(owner, kind, List<Track>::sortByArtist)
}
private fun sort(owner: String, kind: String, sortStrategy: List<Track>.() -> List<Track>) {
val remote = playlist.get(owner, kind)
val tracks = remote.tracks.sortStrategy()
// ...
}
In the above example List<Track>.() -> List<Track> means: function that is an extension over List<Track> and returns List<Track>.
Not really sure, what you want to do there.
I'd suggest to use something like:
val theSortedList = listOfTracks.filter { it.title == "two" }.sortByArtist()
anyway, to pass an extension function as argument to another function and call it from there, here is some example code in your context:
fun List<Track>.sortByTitle(): List<Track> {
return this.sortedBy { it.title }
}
fun List<Track>.sortByArtist(): List<Track> {
return this.sortedBy { it.artists[0].name }
}
fun main() {
println("sortByTitle")
val titleTracks = sort("somebody", "somekind", List<Track>::sortByTitle)
titleTracks.forEach { println("${it.title}: ${it.artists.map { it.name }.joinToString()}") }
println()
println("sortByArtist")
val artistTracks = sort("somebody", "somekind", List<Track>::sortByArtist)
artistTracks.forEach { println("${it.title}: ${it.artists.map { it.name }.joinToString()}") }
}
fun sort(owner: String, kind: String, function: List<Track>.() -> List<Track>): List<Track> {
val listOfTracks = listOf<Track>(
Track("two", listOf(Artist("Madonna"), Artist("Beethoven"))),
Track("one", listOf(Artist("Prince"), Artist("Elvis"))),
)
var sortedTracks: List<Track>
sortedTracks = function.invoke(listOfTracks)
// another way to invoke the function:
sortedTracks = listOfTracks.function()
return sortedTracks
}
data class Playlist(val name: String) {
fun get(artist: Artist, kind: String): List<Artist> = listOf()
fun tracks(): List<Track> = listOf()
}
data class Track(val title: String, val artists: List<Artist>)
data class Artist(val name: String)

How to use Either monad and avoid nested flatMap

I'm in a situation where I'm trying to setup some data and then call a service. Each step can fail, so I'm trying to use Arrow's Either to manage this.
But I'm ending up with a lot of nested flatMaps.
The following code snippet illustrates what I'm trying to do:
import arrow.core.Either
import arrow.core.flatMap
typealias ErrorResponse = String
typealias SuccessResponse = String
data class Foo(val userId: Int, val orderId: Int, val otherField: String)
data class User(val userId: Int, val username: String)
data class Order(val orderId: Int, val otherField: String)
interface MyService {
fun doSomething(foo: Foo, user: User, order: Order): Either<ErrorResponse, SuccessResponse> {
return Either.Right("ok")
}
}
fun parseJson(raw: String): Either<ErrorResponse, Foo> = TODO()
fun lookupUser(userId: Int): Either<ErrorResponse, User> = TODO()
fun lookupOrder(orderId: Int): Either<ErrorResponse, Order> = TODO()
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> {
val foo = parseJson(rawData)
val user = foo.flatMap {
lookupUser(it.userId)
}
//I want to lookupOrder only when foo and lookupUser are successful
val order = user.flatMap {
foo.flatMap { lookupOrder(it.orderId) }
}
//Only when all 3 are successful, call the service
return foo.flatMap { f ->
user.flatMap { u ->
order.flatMap { o ->
myService.doSomething(f, u, o)
}
}
}
}
I'm sure there is a better way to do this. Can someone help me with an idiomatic approach?
You can use the either { } DSL, this is available in a suspend manner or in a non-suspend manner through the either.eager { } builder.
That way you can use suspend fun <E, A> Either<E, A>.bind(): A.
Rewriting your code example:
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> =
either.eager {
val foo = parseJson(rawData).bind()
val user = lookupUser(foo.userId).bind()
val order = lookupOrder(foo.orderId).bind()
myService.doSomething(foo, user, order).bind()
}
If you run into an Either.Left, then bind() will short-circuit the either.eager block and return with the encountered Either.Left value.

How to set up Viewmodel class properly?

I'm trying to follow some tutotial from github about MVVM model and i'm stuck at viewmodel class because there's an error says
Not enough information to infer type variable T
and
Type mismatch.
Required:Resource<Movie>
Found:Unit
And when i check my other class like ApiService, Dao, NetworkBoundResource, ApiResponse, Resources and respository everthing fine like this
ApiService :
interface ApiService {
#GET("3/movie/popular")
fun getMyMovie(#Query("api_key") api : String = "32bbbffe944d16d1d2a3ee46cfc6aaa0"
) : Flow<ApiResponse<MovieResponse.Movie>>
}
MovieDao:
#Dao
interface MovieDao : BaseDao<Movie> {
// #Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertMovie(movie: List<Movie>)
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun getMyMovie() : Flow<Movie>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun findAllMovie() : Maybe<List<Movie>>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun streamAll() : Flowable<List<Movie>>
#Query("DELETE FROM `movie`")
fun deleteAll()
}
MovieRespository:
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() : Flow<Resource<Movie>> {
return networkBoundResource(
fetchFromLocal = { movieDao.getMyMovie() },
shouldFetchFromRemote = {true},
fetchFromRemote = {apiService.getMyMovie()},
processRemoteResponse = {},
saveRemoteData = {movieDao.insert(
it.results.let {
it.map { data -> Movie.from(data) }
}
)},
onFetchFailed = {_, _ ->}
).flowOn(Dispatchers.IO)
}
NeteorkBoundResource:
inline fun <DB, REMOTE> networkBoundResource(
crossinline fetchFromLocal: () -> Flow<DB>,
crossinline shouldFetchFromRemote: (DB?) -> Boolean = { true },
crossinline fetchFromRemote: () -> Flow<ApiResponse<REMOTE>>,
crossinline processRemoteResponse: (response: ApiSuccessResponse<REMOTE>) -> Unit = { Unit },
crossinline saveRemoteData: (REMOTE) -> Unit = { Unit },
crossinline onFetchFailed: (errorBody: String?, statusCode: Int) -> Unit = { _: String?, _: Int -> Unit }
) = flow<Resource<DB>> {
emit(Resource.Loading(null))
val localData = fetchFromLocal().first()
if (shouldFetchFromRemote(localData)) {
emit(Resource.Loading(localData))
fetchFromRemote().collect { apiResponse ->
when (apiResponse) {
is ApiSuccessResponse -> {
processRemoteResponse(apiResponse)
apiResponse.body?.let { saveRemoteData(it) }
emitAll(fetchFromLocal().map { dbData ->
Resource.Success(dbData)
})
}
is ApiErrorResponse -> {
onFetchFailed(apiResponse.errorMessage, apiResponse.statusCode)
emitAll(fetchFromLocal().map {
Resource.Error(
apiResponse.errorMessage,
it
)
})
}
}
}
} else {
emitAll(fetchFromLocal().map { Resource.Success(it) })
}
}
ApiResponse:
sealed class ApiResponse<T> {
companion object {
fun <T> create(error: Throwable): ApiErrorResponse<T> {
return ApiErrorResponse(
error.message ?: "Unknown error",
0
)
}
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val body = response.body()
val headers = response.headers()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body,
headers
)
}
} else {
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(
errorMsg ?: "Unknown error",
response.code()
)
}
}
}
}
/**
* separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiSuccessResponse<T>(
val body: T?,
val headers: okhttp3.Headers
) : ApiResponse<T>()
data class ApiErrorResponse<T>(val errorMessage: String, val statusCode: Int) : ApiResponse<T>()
Resource:
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
// data class Loading<T>(val loadingData: T?) : Resource<T>(Status.LOADING, loadingData, null)
// data class Success<T>(val successData: T?) : Resource<T>(Status.SUCCESS, successData, null)
// data class Error<T>(val msg: String, val error: T?) : Resource<T>(Status.ERROR, error, msg)
companion object {
fun <T> Success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data,null)
}
fun <T> Error(msg: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> Loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
MainViewModel:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Resource.Loading() ->{}
Resource.Success() ->{}
Resource.Error() ->{}
}
}.asLiveData(viewModelScope.coroutineContext)
}
to be specific this what the error looks like and this is the link of tutorial i learn
https://github.com/hadiyarajesh/flower
viewModel Class Error
You get the error, because Inside the when, you are trying to construct a new instance of Resource.Loading() etc, but those require a type, so it would need to be something like Resource.Loading<Movie>().
Tht being said, you are doing when(it.status), so the cases in the when, should not be a Resource.Loading, but Status.LOADING instead:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Status.LOADING ->{}
Status.SUCCESS ->{}
Status.ERROR ->{}
}
return#map it
}.asLiveData(viewModelScope.coroutineContext)
}
Also, since you are declaring LiveData<Resource<Movie>>, you need to return a Resource<Movie> from the map {} (we could drop return#map, it is just to be explicit)

Is it possible to suspend a coroutine with a timeout?

What I want is a function like this:
suspendCoroutineWithTimeout(timeout: Long, unit: TimeUnit, crossinline block: (Continuation<T>) -> Unit)
That does basically the same thing as the existing suspendCoroutine function but if the callback or whatever was provided in the block dosen't get called within the specified timeout the corutine continues but with a TimeoutException or something like that.
You can combine withTimeout and suspendCancellableCoroutine in a straightforward way for the desired effect:
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, unit: TimeUnit,
crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout, unit) {
suspendCancellableCoroutine(block = block)
}
Perfect answer from #Roman Elizarov.. Just adding my 2 cents on it because I needed a return from that call.. So adding T? return it would be ...
suspend inline fun <T> suspendCoroutineWithTimeout(timeout: Long, crossinline block: (Continuation<T>) -> Unit ) : T? {
var finalValue : T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
If you're using suspendCoroutine, that means you have full control over what you do with the continuation you got. For example, you can pass it to the callback-based async API and, additionally, to a scheduled task that will resume it with exception:
suspend fun mySuspendFun(timeout: Long): String {
val didResume = AtomicBoolean()
fun markResumed() = !didResume.getAndSet(true)
return suspendCoroutine { cont ->
launch(CommonPool) {
delay(timeout)
if (markResumed()) {
cont.resumeWithException(TimeoutException())
}
}
// call Async API, and in the callback, use
// if (markResumed()) {
// cont.resume(result)
// }
}
}
However, Kotlin's standard library supports your use case first-class, as described in Roman Elizarov's answer. I suggest you use that approach in your project.
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long,
crossinline block: (CancellableContinuation<T>) -> Unit
): T? {
var finalValue: T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
suspend inline fun <T> suspendCoroutineObserverWithTimeout(
timeout: Long,
data: LiveData<T>,
crossinline block: (T) -> Boolean
): T? {
return suspendCoroutineWithTimeout<T>(timeout) { suspend ->
var observers : Observer<T>? = null
val oldData = data.value
observers = Observer<T> { t ->
if (oldData == t) {
KLog.e("参数一样,直接return")
return#Observer
}
KLog.e("参数不一样,刷新一波")
if (block(t) && !suspend.isCancelled) {
suspend.resume(t)
observers?.let { data.removeObserver(it) }
}
}
data.observeForever(observers)
suspend.invokeOnCancellation {
KLog.e("删除observiers")
observers.let { data.removeObserver(it) }
}
}
}
The previous #Roman Elizarov and #febaisi answers have been answered very well, I added a type judgment and livedata on this basis, and I will return only when the conditions are met. Sorry, my English is not very good. –

How to set a value preference in Kotlin?

The following code is from a sample project about Kotlin, I can use Code 1 to get a value of a shared preferences, but I can set a value of a shared preferences?
I can't find those code in the sample project, could you tell me how I can do? Thanks!
Code 1
class SettingsActivity : AppCompatActivity() {
companion object {
val ZIP_CODE = "zipCode"
val DEFAULT_ZIP = 94043L
}
var zipCode: Long by DelegatesExt.preference(this, ZIP_CODE, DEFAULT_ZIP)
}
Code 2
object DelegatesExt {
fun <T> notNullSingleValue() = NotNullSingleValueVar<T>()
fun <T> preference(context: Context, name: String, default: T) = Preference(context, name, default)
}
class NotNullSingleValueVar<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("${property.name} not initialized")
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = if (this.value == null) value
else throw IllegalStateException("${property.name} already initialized")
}
}
class Preference<T>(val context: Context, val name: String, val default: T) {
val prefs: SharedPreferences by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) }
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return findPreference(name, default)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
}
#Suppress("UNCHECKED_CAST")
private fun findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as T
}
private fun putPreference(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can't be saved into Preferences")
}.apply()
}
}
And More
If the function putPreference is public, I can set value of a shared preferences using the code below, but it's ugly
class SettingsActivity : AppCompatActivity() {
companion object {
val ZIP_CODE = "zipCode"
val DEFAULT_ZIP = 94043L
}
DelegatesExt.Preference(this, ZIP_CODE, DEFAULT_ZIP).putPreference( ZIP_CODE,"99999L");
}
That's what operator fun setValue is for: you just write
activity.zipCode = 1L
(where activity is a SettingsActivity) or
zipCode = 1L
(inside SettingsActivity or a class extending it) and it'll call setValue(activity, activity::zipCode, 1L) which calls putPreference("zipCode", 1L). See https://kotlinlang.org/docs/reference/delegated-properties.html for more.