I am using the Play Services Auth api Phone and so far I have the foll
fun startSmsListener() {
val client = SmsRetriever.getClient(applicationContext /* context */);
val task = client.startSmsRetriever();
task.addOnSuccessListener(object : OnSuccessListener<Void> {
override fun onSuccess(p0: Void?) {
//do somethin
}
})
task.addOnFailureListener(object : OnFailureListener {
override fun onFailure(p0: Exception) {
//Handle error
}
})
}
Now I want to put this in an SmsManager class and convert it into an Single/Observable so I can handle it in a reactive way in my viewmodel. How can I do that?
So far I've got this:
var single = Single.create(SingleOnSubscribe<Void> { e ->
val task = client.startSmsRetriever()
task.addOnSuccessListener {
e.onSuccess(it)
}
task.addOnFailureListener {
e.onError(it)
}
})
But I am unsure as to whether this code is correct or not, whether there is something im missing like removing the listeners after disposal.
Any help?
You are interested in a "boolean" value - either connected or not connected, thus instead of Single you should use Completable:
Completable.create { emitter ->
val client = SmsRetriever.getClient(applicationContext)
val task = client.startSmsRetriever()
task.addOnSuccessListener { emitter.onComplete() }
task.addOnFailureListener { emitter.tryOnError(it) }
}
While creating a Completable manually will work, you might also have a look at the RxTask project. It provides "RxJava 2 binding for Google Play Services Task APIs".
If you need it just in one place, an extra library would certainly be an overkill. But if you plan to use more Play Services together with RxJava, it might be worth a look...
It doesn't (yet) provide a wrapper explicitly for SmsRetriever, but the general task helper classes would probably be enough:
val client = SmsRetriever.getClient(applicationContext)
val smsReceiver = CompletableTask.create(client::startSmsRetriever)
Related
I am reading this article to fully understand the dos and donts of using Flow while comparing it to my implementation, but I can't grasp clearly how to tell if you are wasting resource when using Flow or flow builder. When is the time a flow is being release/freed in memory and when is the time that you are wasting resource like accidentally creating multiple instances of flow and not releasing them?
I have a UseCase class that invokes a repository function that returns Flow. In my ViewModel this is how it looks like.
class AssetViewModel constructor(private val getAssetsUseCase: GetAssetsUseCase) : BaseViewModel() {
private var job: Job? = null
private val _assetState = defaultMutableSharedFlow<AssetState>()
fun getAssetState() = _assetState.asSharedFlow()
init {
job = viewModelScope.launch {
while(true) {
if (lifecycleState == LifeCycleState.ON_START || lifecycleState == LifeCycleState.ON_RESUME)
fetchAssets()
delay(10_000)
}
}
}
fun fetchAssets() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAssetsUseCase(
AppConfigs.ASSET_BASE_URL,
AppConfigs.ASSET_PARAMS,
AppConfigs.ASSET_SIZES[AppConfigs.ASSET_LIMIT_INDEX]
).onEach {
when(it){
is RequestStatus.Loading -> {
_assetState.tryEmit(AssetState.FetchLoading)
}
is RequestStatus.Success -> {
_assetState.tryEmit(AssetState.FetchSuccess(it.data.assetDataDomain))
}
is RequestStatus.Failed -> {
_assetState.tryEmit(AssetState.FetchFailed(it.message))
}
}
}.collect()
}
}
}
override fun onCleared() {
job?.cancel()
super.onCleared()
}
}
The idea here is we are fetching data from remote every 10 seconds while also allowing on demand fetch of data via UI.
Just a typical useless UseCase class
class GetAssetsUseCase #Inject constructor(
private val repository: AssetsRepository // Passing interface not implementation for fake test
) {
operator fun invoke(baseUrl: String, query: String, limit: String): Flow<RequestStatus<AssetDomain>> {
return repository.fetchAssets(baseUrl, query, limit)
}
}
The concrete implementation of repository
class AssetsRepositoryImpl constructor(
private val service: CryptoService,
private val mapper: AssetDtoMapper
) : AssetsRepository {
override fun fetchAssets(
baseUrl: String,
query: String,
limit: String
) = flow {
try {
emit(RequestStatus.Loading())
val domainModel = mapper.mapToDomainModel(
service.getAssetItems(
baseUrl,
query,
limit
)
)
emit(RequestStatus.Success(domainModel))
} catch (e: HttpException) {
emit(RequestStatus.Failed(e))
} catch (e: IOException) {
emit(RequestStatus.Failed(e))
}
}
}
After reading this article which says that using stateIn or sharedIn will improve the performance when using a flow, it seems that I am creating new instances of the same flow on-demand. But there is a limitation as the stated approach only works for variable and not function that returns Flow.
stateIn and shareIn can save resources if there are multiple observers, by avoiding redundant fetching. And in your case, you could set it up to automatically pause the automatic re-fetching when there are no observers. If, on the UI side you use repeatOnLifecycle, then it will automatically drop your observers when the view is off screen and then you will avoid wasted fetches the user will never see.
I think it’s not often described this way, but often the multiple observers are just observers coming from the same Activity or Fragment class after screen rotations or rapidly switching between fragments. If you use WhileSubscribed with a timeout to account for this, you can avoid having to restart your flow if it’s needed again quickly.
Currently you emit to from an external coroutine instead of using shareIn, so there’s no opportunity to pause execution.
I haven't tried to create something that supports both automatic and manual refetching. Here's a possible strategy, but I haven't tested it.
private val refreshRequest = Channel<Unit>(Channel.CONFLATED)
fun fetchAssets() {
refreshRequest.trySend(Unit)
}
val assetState = flow {
while(true) {
getAssetsUseCase(
AppConfigs.ASSET_BASE_URL,
AppConfigs.ASSET_PARAMS,
AppConfigs.ASSET_SIZES[AppConfigs.ASSET_LIMIT_INDEX]
).map {
when(it){
is RequestStatus.Loading -> AssetState.FetchLoading
is RequestStatus.Success -> AssetState.FetchSuccess(it.data.assetDataDomain)
is RequestStatus.Failed -> AssetState.FetchFailed(it.message)
}
}.emitAll()
withTimeoutOrNull(100L) {
// drop any immediate or pending manual request
refreshRequest.receive()
}
// Wait until a fetch is manually requested or ten seconds pass:
withTimeoutOrNull(10000L - 100L) {
refreshRequest.receive()
}
}
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(4000L), replay = 1)
To this I would recommend not using flow as the return type of the usecase function and the api call must not be wrapped inside a flow builder.
Why:
The api call actually is happening once and then again after an interval it is triggered by the view model itself, returning flow from the api caller function will be a bad usage of powerful tool that is actually meant to be called once and then it must be self-reliant, it should emit or pump in the data till the moment it has a subscriber/collector.
One usecase you can consider when using flow as return type from the room db query call, it is called only once and then the room emits data into it till the time it has subscriber.
.....
fun fetchAssets() {
viewModelScope.launch {
// loading true
val result=getusecase(.....)
when(result){..process result and emit on state..}
// loading false
}
}
.....
suspend operator fun invoke(....):RequestStatus<AssetDomain>{
repository.fetchAssets(baseUrl, query, limit)
}
.....
override fun fetchAssets(
baseUrl: String,
query: String,
limit: String
):RequestStatus {
try {
//RequestStatus.Loading()//this can be managed in viewmodel itself
val domainModel = mapper.mapToDomainModel(
service.getAssetItems(
baseUrl,
query,
limit
)
)
RequestStatus.Success(domainModel)
} catch (e: HttpException) {
RequestStatus.Failed(e)
} catch (e: IOException) {
RequestStatus.Failed(e)
}
}
In my project I write View and ViewModel natively and share Repository, Db, networking.
When user navigates from one screen to another, I want to cancel all network requests or other heavy background operations that are currently running in the first screen.
Example function in Repository class:
#Throws(Throwable::class)
suspend fun fetchData(): List<String>
In Android's ViewModel I can use viewModelScope to automatically cancel all active coroutines. But how to cancel those tasks in iOS app?
Lets suppose that the object session is a URLSession instance, you can cancel it by:
session.invalidateAndCancel()
I didn't find any first party information about this or any good solution, so I came up with my own. Shortly, it will require turning repository suspend functions to regular functions with return type of custom interface that has cancel() member function. Function will take action lambda as parameter. On implementation side, coroutine will be launched and reference for Job will be kept so later when it is required to stop background work interface cancel() function will cancel job.
In addition, because it is very hard to read type of error (in case it happens) from NSError, I wrapped return data with custom class which will hold error message and type. Earlier I asked related question but got no good answer for my case where ViewModel is written natively in each platform.
If you find any problems with this approach or have any ideas please share.
Custom return data wrapper:
class Result<T>(
val status: Status,
val value: T? = null,
val error: KError? = null
)
enum class Status {
SUCCESS, FAIL
}
data class KError(
val type: ErrorType,
val message: String? = null,
)
enum class ErrorType {
UNAUTHORIZED, CANCELED, OTHER
}
Custom interface
interface Cancelable {
fun cancel()
}
Repository interface:
//Convert this code inside of Repository interface:
#Throws(Throwable::class)
suspend fun fetchData(): List<String>
//To this:
fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable
Repository implementation:
override fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable = runInsideOfCancelableCoroutine {
val result = executeAndHandleExceptions {
val data = networkExample()
// do mapping, db operations, etc.
data
}
action.invoke(result)
}
// example of doing heavy background work
private suspend fun networkExample(): List<String> {
// delay, thread sleep
return listOf("data 1", "data 2", "data 3")
}
// generic function for reuse
private fun runInsideOfCancelableCoroutine(task: suspend () -> Unit): Cancelable {
val job = Job()
CoroutineScope(Dispatchers.Main + job).launch {
ensureActive()
task.invoke()
}
return object : Cancelable {
override fun cancel() {
job.cancel()
}
}
}
// generic function for reuse
private suspend fun <T> executeAndHandleExceptions(action: suspend () -> T?): Result<T> {
return try {
val data = action.invoke()
Result(status = Status.SUCCESS, value = data, error = null)
} catch (t: Throwable) {
Result(status = Status.FAIL, value = null, error = ErrorHandler.getError(t))
}
}
ErrorHandler:
object ErrorHandler {
fun getError(t: Throwable): KError {
when (t) {
is ClientRequestException -> {
try {
when (t.response.status.value) {
401 -> return KError(ErrorType.UNAUTHORIZED)
}
} catch (t: Throwable) {
}
}
is CancellationException -> {
return KError(ErrorType.CANCELED)
}
}
return KError(ErrorType.OTHER, t.stackTraceToString())
}
}
You probably have 3 options:
If you're using a some sort of reactive set up iOS side (e.g. MVVM) you could just choose to ignore cancellation. Cancellation will only save a minimal amount of work.
Wrap your iOS calls to shared code in an iOS reactive framework (e.g. combine) and handle cancellation using the iOS framework. The shared work would still be done, but the view won't be updated as your iOS framework is handling cancellation when leaving the screen.
Use Flow with this closable helper
With RxJava we can do something like this:
BaseViewModel
protected void subscribe(Completable completable, MutableLiveData<Response> response) {
mDisposable.add(
completable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
() -> response.setValue(Response.success(true)),
e -> response.setValue(Response.error(e))
)
);
}
protected <T> void subscribe(Single<T> single, MutableLiveData<Response> response) {
mDisposable.add(
single.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
result -> response.setValue(Response.success(result)),
e -> response.setValue(Response.error(e))
)
);
}
Then, from repository we getting Single/Complete and pass it to our custom subscribe(), then we get generic Result with data(optional), very easy way to work with asynchronous requests.
How we can abstract coroutines with similar structure, instead of write Launch in every method in ViewModel and try/catch error manually?
Instead of closely following the code you already have with minimal adaptations, I suggest you review your design altogether when migrating to coroutines.
One important principle embedded into coroutines is structured concurrency. This isn't just about the coroutine scopes and cancellation, it is also about the use of futures by any name (be it CompletionStage, Deferred, Task, Single or any other). According to structured concurrency, a future is basically equivalent to a live thread that has no defined scope. You should avoid them.
Instead you should have clearly delineated places in the code that launch new concurrent work contained within a single top-level block of code provided at the launch site.
So far, that implies that you do have a launch block at each entry point into your code from the Android framework, and that's a lot of places due to the nature of the callback-oriented programming model.
However, everything within that block should be coded according to structured concurrency. If you have just one network call to make, your code is entirely sequential: make the call, get the response, process it. The network calls themselves become suspend functions that complete with the result of the call and do not accept callbacks. All the traditional design patterns from the world of blocking calls apply here.
See here for an intro to using coroutines with LiveData, it may help you map your design to the coroutine-oriented one:
https://developer.android.com/topic/libraries/architecture/coroutines#livedata
You are probably looking for something like this
CoroutineWrapper
fun <T> ViewModel.apiCx(context: CoroutineContext = Dispatchers.Default, init: suspend CxWrapper<T>.() -> Unit) {
val wrap = CxWrapper<T>(context)
wrap.launch {
try {
init.invoke(wrap)
callCx(wrap)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun <T> callCx(wrap: CxWrapper<T>) {
val response: Response<T>? = wrap.request
response?.let {
if (it.isSuccessful) {
wrap.success(it.body())
} else {
wrap.fail(Pair(it.code(), it.message()))
}
}
}
class CxWrapper<T>(override val coroutineContext: CoroutineContext) : CoroutineScope {
var request: Response<T>? = null
internal var success: (T?) -> Unit = {}
internal var fail: (Pair<Int, String?>) -> Unit = {}
fun success(onSuccess: (T?) -> Unit) {
success = onSuccess
}
fun error(onError: (Pair<Int, String?>) -> Unit) {
fail = onError
}
}
you can have this as a separate helper class and to use this from your ViewModel
apiCx<YourModelClass> {
request = yourApiCall()
success { yourModelClass ->
Log.d(TAG, "success")
}
error {
Log.e(TAG, "error")
}
}
You would just do the same, just adapted to coroutines. Just replace the different stream types with the suspension methods you need.
protected inline fun <T> MutableLiveData<Response>.subscribe(single: suspend () -> T) {
viewModelScope.launch {
try {
value = Response.loading()
value = withContext(Dispatchers.IO) {
Response.success(single())
}
} catch(e: Throwable) {
value = Response.error(e)
} finally {
value = Response.idle()
}
}
To use it just call with the livedata as receiver
responseLiveData.subscribe<T> {
singleFromRepo()
}
responseLiveData.subscribe<Unit> {
completableFromRepo()
}
What is a proper way to communicate between the ViewModel and the View, Google architecture components give use LiveData in which the view subscribes to the changes and update itself accordingly, but this communication not suitable for single events, for example show message, show progress, hide progress etc.
There are some hacks like SingleLiveEvent in Googles example but it work only for 1 observer.
Some developers using EventBus but i think it can quickly get out of control when the project grows.
Is there a convenience and correct way to implement it, how do you implement it?
(Java examples welcome too)
Yeah I agree, SingleLiveEvent is a hacky solution and EventBus (in my experience) always lead to trouble.
I found a class called ConsumableValue a while back when reading the Google CodeLabs for Kotlin Coroutines, and I found it to be a good, clean solution that has served me well (ConsumableValue.kt):
class ConsumableValue<T>(private val data: T) {
private var consumed = false
/**
* Process this event, will only be called once
*/
#UiThread
fun handle(block: ConsumableValue<T>.(T) -> Unit) {
val wasConsumed = consumed
consumed = true
if (!wasConsumed) {
this.block(data)
}
}
/**
* Inside a handle lambda, you may call this if you discover that you cannot handle
* the event right now. It will mark the event as available to be handled by another handler.
*/
#UiThread
fun ConsumableValue<T>.markUnhandled() {
consumed = false
}
}
class MyViewModel : ViewModel {
private val _oneShotEvent = MutableLiveData<ConsumableValue<String>>()
val oneShotEvent: LiveData<ConsumableValue<String>>() = _oneShotData
fun fireEvent(msg: String) {
_oneShotEvent.value = ConsumableValue(msg)
}
}
// In Fragment or Activity
viewModel.oneShotEvent.observe(this, Observer { value ->
value?.handle { Log("TAG", "Message:$it")}
})
In short, the handle {...} block will only be called once, so there's no need for clearing the value if you return to a screen.
What about using Kotlin Flow?
I do not believe they have the same behavior that LiveData has where it would alway give you the latest value. Its just a subscription similar to the workaround SingleLiveEvent for LiveData.
Here is a video explaining the difference that I think you will find interesting and answer your questions
https://youtu.be/B8ppnjGPAGE?t=535
try this:
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
And wrapper it into LiveData
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
And observe
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})
link reference: Use an Event wrapper
For showing/hiding progress dialogs and showing error messages from a failed network call on loading of the screen, you can use a wrapper that encapsulates the LiveData that the View is observing.
Details about this method are in the addendum to app architecture:
https://developer.android.com/jetpack/docs/guide#addendum
Define a Resource:
data class Resource<out T> constructor(
val state: ResourceState,
val data: T? = null,
val message: String? = null
)
And a ResourceState:
sealed class ResourceState {
object LOADING : ResourceState()
object SUCCESS : ResourceState()
object ERROR : ResourceState()
}
In the ViewModel, define your LiveData with the model wrapped in a Resource:
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
Also in the ViewModel, define the method that makes the API call to load the data for the current screen:
fun loadDataForView() = compositeDisposable.add(
exampleUseCase.exampleApiCall()
.doOnSubscribe {
exampleLiveData.setLoading()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
exampleLiveData.setSuccess(it)
},
{
exampleLiveData.setError(it.message)
}
)
)
In the View, set up the Observer on creation:
viewModel.exampleLiveData.observe(this, Observer {
updateResponse(it)
})
Here is the example updateResponse() method, showing/hiding progress, and showing an error if appropriate:
private fun updateResponse(resource: Resource<ExampleModel>?) {
resource?.let {
when (it.state) {
ResourceState.LOADING -> {
showProgress()
}
ResourceState.SUCCESS -> {
hideProgress()
// Use data to populate data on screen
// it.data will have the data of type ExampleModel
}
ResourceState.ERROR -> {
hideProgress()
// Show error message
// it.message will have the error message
}
}
}
}
You can easily achieve this by not using LiveData, and instead using Event-Emitter library that I wrote specifically to solve this problem without relying on LiveData (which is an anti-pattern outlined by Google, and I am not aware of any other relevant alternatives).
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
implementation 'com.github.Zhuinden:event-emitter:1.0.0'
If you also copy the LiveEvent class , then now you can do
private val emitter: EventEmitter<String> = EventEmitter()
val events: EventSource<String> get() = emitter
fun doSomething() {
emitter.emit("hello")
}
And
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = getViewModel<MyViewModel>()
viewModel.events.observe(viewLifecycleOwner) { event ->
// ...
}
}
// inline fun <reified T: ViewModel> Fragment.getViewModel(): T = ViewModelProviders.of(this).get(T::class.java)
For rationale, you can check out my article I wrote to explain why the alternatives aren't as valid approaches.
You can however nowadays also use a Channel(UNLIMITED) and expose it as a flow using asFlow(). That wasn't really applicable back in 2019.
I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.