I´ve been looking for a suitable solution or best practice when I want to use Kotlin Flows with ordinary callbacks. My use case is that I write a kotlin library that uses Kotlin Flow internally and i have to assume that the users will use Java for instance. So I thought that the best solution is to overload a basic callback interface to my flow method and call it in collect something like this:
class KotlinClass {
interface Callback {
fun onResult(result: Int)
}
private fun foo() = flow {
for (i in 1..3) {
emit(i)
}
}
fun bar(callback: Callback) {
runBlocking {
foo().collect { callback.onResult(it) }
}
}
private fun main() {
bar(object : Callback {
override fun onResult(result: Int) {
TODO("Not yet implemented")
}
})
}
and in my Java Application i can simply use it like that:
public class JavaClass {
public void main() {
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(new KotlinClass.Callback() {
#Override
public void onResult(int result) {
// TODO("Not yet implemented")
}
});
}
}
I am not sure whats the way to go because I would like to have my Kotlin library that uses Flows usable in a good fashion for Java and Kotlin.
I came across callbackFlow but that seems to be only if I want to let´s call it flow-ify a callback-based API? Because I am quite new to Kotlin and Flows please apologise if my question is flawed in cause of missing some basic concepts of kotlin.
I would give the Java client more control over the flow. I would add a onStart and onCompletion method to your callback interface. Beside this I would use an own CoroutineScope - maybe customizable from the Java client. And I would not block the calling thread from within the Kotlin function - no runBlocking.
#InternalCoroutinesApi
class KotlinClass {
val coroutineScope = CoroutineScope(Dispatchers.Default)
interface FlowCallback {
#JvmDefault
fun onStart() = Unit
#JvmDefault
fun onCompletion(thr: Throwable?) = Unit
fun onResult(result: Int)
}
private fun foo() = flow {
for (i in 1..3) {
emit(i)
}
}
fun bar(flowCallback: FlowCallback) {
coroutineScope.launch {
foo().onStart { flowCallback.onStart() }
.onCompletion { flowCallback.onCompletion(it) }
.collect { flowCallback.onResult(it) }
}
}
fun close() {
coroutineScope.cancel()
}
}
Now the Java client is in full control how to start, collect and cancel the flow. For example you could use a latch to wait for completion, set an timeout and cancel the couroutine scope. This looks in the first place like a lot of code, but typically you will need this kind of flexibility.
public class JavaClass {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(new KotlinClass.FlowCallback() {
#Override
public void onCompletion(#Nullable Throwable thr) {
latch.countDown();
}
#Override
public void onResult(int result) {
System.out.println(result);
}
});
try {
latch.await(5, TimeUnit.SECONDS);
} finally {
libraryClass.close();
}
}
}
You don't need to create a interface in the Kotlin code. You can define bar like that:
fun bar(callback: (Int) -> Unit) {
runBlocking {
foo().collect { callback(it) }
}
}
From the Java code you can call the function like that:
public class JavaClass {
public static void main(String[] args) {
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(v -> { System.out.println(v); return Unit.INSTANCE; });
}
}
In case anyone wondering for a general solution. Here's our version of enhancement from #rene answer here.
Accept a generic type
A configurable coroutineScope
// JavaFlow.kt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
#InternalCoroutinesApi
class JavaFlow<T>(
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) {
interface OperatorCallback <T> {
#JvmDefault
fun onStart() = Unit
#JvmDefault
fun onCompletion(thr: Throwable?) = Unit
fun onResult(result: T)
}
fun collect(
flow: Flow<T>,
operatorCallback: OperatorCallback<T>,
) {
coroutineScope.launch {
flow
.onStart { operatorCallback.onStart() }
.onCompletion { operatorCallback.onCompletion(it) }
.collect { operatorCallback.onResult(it) }
}
}
fun close() {
coroutineScope.cancel()
}
}
Java caller-side:
// code omitted...
new JavaFlow<File>().collect(
// compressImageAsFlow is our actual kotlin flow extension
FileUtils.compressImageAsFlow(file, activity),
new JavaFlow.OperatorCallback<File>() {
#Override
public void onResult(File result) {
// do something with the result here
SafeSingleton.setFile(result);
}
}
);
// or using lambda with method references
// new JavaFlow<File>().collect(
// FileUtils.compressImageAsFlow(file, activity),
// SafeSingleton::setFile
// );
// Change coroutineScope to Main
// new JavaFlow<File>(CoroutineScopeKt.MainScope()).collect(
// FileUtils.compressImageAsFlow(file, activity),
// SafeSingleton::setFile
// );
OperatorCallback.onStart and OperatorCallback.onCompletion is optional, override it as needed.
Related
I'm trying to instrument Kotlin coroutines, similar to what's done here using a Javaagent. I don't want a Javaagent.
The first step is to intercept the creation, suspension and resumption of Coroutines defined in the DebugProbes. The code for that is as follows:
public class Instrumentor {
private static final Logger LOG = LoggerFactory.getLogger(Instrumentor.class);
public static void install() {
TypeDescription typeDescription = TypePool.Default.ofSystemLoader()
.describe("kotlin.coroutines.jvm.internal.DebugProbesKt")
.resolve();
new ByteBuddy()
.redefine(typeDescription, ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("probeCoroutineCreated").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineCreatedAdvice.class))
.method(ElementMatchers.named("probeCoroutineResumed").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineResumedAdvice.class))
.method(ElementMatchers.named("probeCoroutineSuspended").and(ElementMatchers.takesArguments(1)))
.intercept(MethodDelegation.to(CoroutineSuspendedAdvice.class))
.make()
.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
DebugProbes.INSTANCE.install();
}
public static void uninstall() {
DebugProbes.INSTANCE.uninstall();
}
public static class CoroutineCreatedAdvice {
#Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static Continuation<Object> exit(#Advice.Return(readOnly = false) Continuation<Object> retVal) {
LOG.info("Coroutine created: {}", retVal);
return retVal;
}
}
public static class CoroutineResumedAdvice {
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(#Advice.Argument(0) final Continuation<Object> continuation) {
LOG.info("Coroutine resumed: {}", continuation);
}
}
public static class CoroutineSuspendedAdvice {
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter(#Advice.Argument(0) final Continuation<Object> continuation) {
LOG.info("Coroutine suspended: {}", continuation);
}
}
}
JUnit5 test to trigger interception:
class CoroutineInstrumentationTest {
companion object {
#JvmStatic
#BeforeAll
fun beforeAll() {
Instrumentor.install()
}
#JvmStatic
#AfterAll
fun afterAll() {
Instrumentor.uninstall()
}
}
#Test
fun testInterception() {
runBlocking {
println("Test")
}
}
}
However, no interception happens (confirmed by the absence of log statements and by using a debugger). I'm new to Byte Buddy, so it's possible I'm missing something. Any ideas?
Kotlin v1.4.10, Kotlin Coroutines v1.3.9, Byte Buddy v1.10.17.
Are you sure the class is not yet loaded at this point? Try setting a breakpoint in ClassInjector.UsingReflection to see if you acutally walk through or of the injection is aborted due to a previously loaded class.
The cleaner solution would be a Java agent. You can use byte-buddy-agent to create one dynamically by ByteBuddyAgent.install() and then register an AgentBuilder on it.
I am wondering what is the best way to unify the response type returned from both a room db and an API. Basically I am trying to get a list of Movie objects
Because of the API response design, I have to wrap the results in a Observable<MovieResponse> object
data class MovieResponse(#SerializedName("total_results") var totalResults: Int,
#SerializedName("total_pages") var totalPages: Int,
#SerializedName("results") var results: List<Movie>) {
}
So, then I can store the Movies in a local Room database, so the result from querying the DB is a Observable<List<Movie>> object.
This is forcing me of having 2 different methods (almost the same) to subscribe to both the API and DB responses
private fun subscribeMovies(moviesObservable: Observable<MovieResponse>) {
disposable.add(moviesObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<MovieResponse>() {
override fun onComplete() {
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onNext(movieResponse: MovieResponse) {
// do stuff
}
}))
}
private fun subscribeMoviesFromDB(moviesObservable: Observable<List<Movie>>) {
disposable.add(moviesObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<List<Movie>>() {
override fun onComplete() {
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onNext(movies: List<Movie>) {
//do stuff
}
}))
}
Any advice on how to deal with this properly?
Thanks
You could make some sort of class that extends what you need. I'm not sure about the physical subscriptions, but at the very least you could make a generic class that you can throw into the subscribeWith() function that's not redundant.
How about something like this? (Sorry I don't know kotlin, so here's a java equivalent):
public class MyObserver<T> extends DisposableObserver<T> {
#Override
public void onComplete() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace()
}
#Override
public void onNext(T response) {
// do stuff
}
}
Then you could in theory call it in each of your subscriptions like such:
disposable.add(moviesObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : MyObserver<List<Movie>>() {
My app is a basic news app which fetches data from JSON provided by Guardian API.
I parsed the values from JSON using raw java code (not using retrofit).
Then I get the LiveData in NewsFeedViewModel class which extends as AndroidViewModel.
And then in the fragment, I submit list to adapter.
These are the issues I'm facing:
1) at first, if the articles to show is set to 10, then if i go to settings and change it to 2, then the last 8 articles are disappearing but the white space /gap is not going. I can still scroll through the empty gap.
2) if i change the number of articles value constantly, then app is becoming un-scrollable.
And i have a few more doubts, how to refresh the data manually when swipeToRefresh is happened?
This is my project github link: https://github.com/sdzshn3/News24-7-RV
Video sample of the issue happening in app: https://drive.google.com/file/d/1gr_fabS2rqREuyecvGSG3IQ_jXOowlW7/view?usp=drivesdk
In kotlin style:
class RefreshableLiveData<T>(
private val source: () -> LiveData<T>
) : MediatorLiveData<T>() {
private var liveData = source()
init {
this.addSource(liveData, ::observer)
}
private fun observer(data: T) {
value = data
}
fun refresh() {
this.removeSource(liveData)
liveData = source()
this.addSource(liveData, ::observer)
}
}
Example:
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.goals.observe(this) { result ->
// update UI
}
// refresh
viewModel.refresh()
}
}
class MyViewModel(useCase: MyUseCase): ViewModel() {
private val _goals = RefreshableLiveData {
useCase.getGoals()
}
val goals: LiveData<Result<List<GoalItem>>>
get() = _goals.map(GoalItem::fromEntity)
fun refresh() {
_goals.refresh()
}
}
class MyUseCase {...}
...
You need to do exactly what I did in this Reddit post:
public class RefreshLiveData<T> extends MutableLiveData<T> {
public interface RefreshAction<T> {
private interface Callback<T> {
void onDataLoaded(T t);
}
void loadData(Callback<T> callback);
}
private final RefreshAction<T> refreshAction;
private final Callback<T> callback = new RefreshAction.Callback<T>() {
#Override
public void onDataLoaded(T t) {
postValue(t);
}
};
public RefreshLiveData(RefreshAction<T> refreshAction) {
this.refreshAction = refreshAction;
}
public final void refresh() {
refreshAction.loadData(callback);
}
}
Then you can do
public class YourViewModel extends ViewModel {
private final GithubRepository githubRepository;
public YourViewModel(GithubRepository githubRepository, SavedStateHandle savedStateHandle) {
this.githubRepository = githubRepository;
}
private final LiveData<String> userId = savedStateHandle.getLiveData("userId"); // from args
private final RefreshLiveData<List<Project>> refreshLiveData = Transformations.switchMap(userId, (uId) -> {
return githubRepository.getProjectList(uId);
});
public void refreshData() {
refreshLiveData.refresh();
}
public LiveData<List<Project>> getProjects() {
return refreshLiveData;
}
}
And then repository can do:
public RefreshLiveData<List<Project>> getProjectList(String userId) {
final RefreshLiveData<List<Project>> liveData = new RefreshLiveData<>((callback) -> {
githubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
callback.onDataLoaded(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
}
});
});
return liveData;
}
I'm trying to save object in background by using Parse.com but I can't override
override fun done(e: ParseException?) {
//code
}
I'm getting error: Modifier 'override' is not applicable to 'local function
In java I would use:
myObject.saveInBackground(new SaveCallback() {
public void done(ParseException e) {
if (e == null) {
myObjectSavedSuccessfully();
} else {
myObjectSaveDidNotSucceed();
}
}
});
Here is my whole class
class StarterApplication : Application() {
override fun onCreate() {
super.onCreate()
Parse.initialize(Parse.Configuration.Builder(this)
.applicationId(appID)
.clientKey(null)
.server(serverUrl)
.build()
)
var exampleObject: ParseObject = ParseObject("ExampleObject")
exampleObject.put("myString", "fwfwe")
exampleObject.saveInBackground( {
override fun done(e: ParseException?) { //here is an error //`Modifier 'override' is not applicable to 'local function`
}
})
}
}
Just do like that:
exampleObject.saveInBackground(object : SaveCallback {
override fun done(e: ParseException?) {
// Add your code here
}
})
In Java, you declare an anonymous class that extend SaveCallback. In Kotlin, you do this with Object Expressions.
you can also try the simplest solution like this
exampleObject.saveInBackground({
//you code here
})
https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
Suppose I have a Java inteface
public interface Bar {
public void baz(String st)
public void jaz()
}
I want to implement above interface anonymously in scala within a function body like:
def foo() = {
val bar : Bar = new Bar() {
// how to do that ?
}
}
If I had to, I'd write it as:
val bar = new Bar {
def baz(st: String): Unit = {
// method impl
}
def jaz(): Unit = {
// method impl
}
}
Though my preference is to avoid side-effecting methods as much as possible, they don't play very nicely with functional programming
val bar = new Bar {
def baz(st: String) {
// method impl
}
def jaz() {
// method impl
}
}