Elegant way of validating permission in Android using MVVM - android

So, I'm using MVVM architecture with Repository pattern to make different API calls.
I have a Repository called X, where I have different related API calls.
Before any of these calls are made, I would like to do validation. If that proceeds successfully, only then network request should be made.
fun getSomethingX(data: Data): Single<Data> {
return if (validation(data)) {
service.getSomethingX()
.onErrorResumeNext(...)
.map { ... ->
...
}
} else {
Single.just(null)
}
}
fun getSomethingY(data: Data): Single<Data> {
return if (validation(data)) {
service.getSomethingX()
.onErrorResumeNext(...)
.map { ... ->
...
}
} else {
Single.just(null)
}
}
As you can see I might have many network request functions called getSomething..(). I see this as a boiler-plate code.
Is there some other way of dealing with validation (not only token validation but permission in general)? If so, can you show me an example?
Is it okay to do permission validation in the Repository level?
Maybe a better approach would be doing validation in Interceptor? But I don't see a clean way of canceling the request if validation does not pass.

A better approach will be to keep the validation at repository level only and keeping your viewmodel dumb as possible. It’s very simply with Kotlin’s Function literals with receiver.
In your repository
fun getSomethingX(
data: Data,
onSuccess: (Single<Data>) -> Unit,
onError: (String) -> Unit
) {
if (validation(data)) {
// Do the network call
onSuccess(//pass result)
} else onError(“Invalid data”)
}
In your ViewModel
repository.getSomethingX(
data,
onSuccess = {
//it will give you Single<Data>
//Update the value
},
onError = {
//Emit error to view
}
)

Related

Kotlin Flow not collected anymore after working initially

Basically I want to make a network request when initiated by the user, collect the Flow returned by the repository and run some code depending on the result. My current setup looks like this:
Viewmodel
private val _requestResult = MutableSharedFlow<Result<Data>>()
val requestResult = _requestResult.filterNotNull().shareIn(
scope = viewModelScope,
started = SharingStarted.WhileViewSubscribed,
replay = 0
)
fun makeRequest() {
viewModelScope.launch {
repository.makeRequest().collect { _requestResult.emit(it) }
}
}
Fragment
buttonLayout.listener = object : BottomButtonLayout.Listener {
override fun onButtonClick() {
viewModel.makeRequest()
}
}
lifecycleScope.launchWhenCreated {
viewModel.requestResult.collect { result ->
when (result) {
Result.Loading -> {
doStuff()
}
is Result.Success -> {
doDifferentStuff(result.data)
}
is Result.Failure -> {
handleError()
}
}
}
}
The first time the request is made everything seems to work. But starting with the second time the collect block in the fragment does not run anymore. The request is still made, the repository returns the flow as expected, the collect block in the viewmodel runs and emit() also seems to be executed successfully.
So what could be the problem here? Something about the coroutine scopes? Admittedly I lack any sort of deeper understanding of the matter at hand.
Also is there a more efficient way of accomplishing what I'm attempting using Kotlin Flows in general? Collecting a flow and then emitting the same flow again seems a bit counterintuitive.
Thanks in advance:)
According to the documentation there are two recommended alternatives:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
//your thing
}
}
I rather the other alternative:
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeReques().flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
I like the flowWithLifecycle shorter syntax and less boiler plate. Be carefull thar is bloking so you cant have anything after that.
The oficial docs
https://developer.android.com/topic/libraries/architecture/coroutines
Please be aware you need the lifecycle aware library.

How to save data from a Kotlin asynchronous closure?

I am calling an API in an asynchronous function, and would like to store the response from the callback.
Specifically, I am using the API category of AWS Amplify in an Android app, and would like to assign the return value to a LiveData variable in a ViewModel.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
return exampleData
}
I can receive the response and it gets logged correctly, but the response is not assigned to the exampleData variable, since the function returns early.
In Android Studio, the variable exampleData is highlighted with the text:
Wrapped into a reference object to be modified when captured in a closure
As I am not that familiar with the multithreading APIs in kotlin, I am not sure, how to block the function until the remote API returns its asynchronous response.
The most basic way of doing this would be with standard Java thread safety constructs.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
val latch = CountDownLatch(1)
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
latch.countDown()
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
latch.countDown()
}
)
latch.await()
return exampleData
}
Since this is on Android, this is probably a bad solution. I'm going to guess that getMuscleGroup is being called on the UI thread, and you do not want this method to actually block. The UI would freeze until the network call completes.
The more Kotlin way of doing this would be to make the method a suspend method.
suspend fun getMuscleGroup(id: String): ExampleData {
return suspendCoroutine { continuation ->
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
continuation.resume(response.data)
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
// return default data
continuation.resume(ExampleData.builder().name("").build())
}
}
}
This use Kotlin coroutines to suspend the coroutine until an answer is ready and then return the results.
Other options would be to use callbacks instead of return values or an observable pattern like LiveData or RxJava.
You cannot block to wait for the asynchronous result. At worst it can cause an Application Not Responding (ANR) error message to appear for the user, and at best makes your app look stuttery and feel unresponsive.
You can instead add a callback for this function:
fun getMuscleGroup(id: String, callback: (ExampleData) -> Unit) {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
callback(response.data)
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
}
And then in the spot where you call the code, you put your subsequent actions in the callback:
fun onMuscleGroupClicked(id: String) {
getMuscleGroup(id) { exampleData ->
// do something with the data after it arrives
}
}
Coroutines are another option. They're nice because you don't have to nest your sequential actions in callbacks. To set it up, I would create a suspend extension function version of the API library query function by using suspendCancellableCoroutine. Then you can freely use it in other suspend functions in an orderly way. But you'll need to read the documentation on coroutines. It's to much to explain here from scratch.

How to wait for multiple jobs in Android ViewModel?

Given the following components
data class Account(val name: String)
data class GetAccountRequest(val name: String)
#Dao
interface AccountDao {
#Query("SELECT * FROM accounts ORDER BY name ASC")
fun all(): LiveData<List<Account>>
}
interface AccountOperations {
#GET("/foo/account")
suspend fun getAccount(#Body request: GetAccountRequest): Account
}
class AccountRepository(private val dao: AccountDao, private val api: AccountOperations) {
val accounts: LiveData<List<Account>> = dao.all()
suspend fun refresh(name: String) {
val account = api.getAccount(GetAccountRequest(name))
dao.insert(account)
}
}
I am working on an Android application that is using these components (powered by Room for the database and Retrofit for API access).
In my ViewModel I maintain a RecyclerView that lists all accounts. I enable users to refresh that list manually. The respective (part of the) ViewModel looks like this:
fun refresh() {
viewModelScope.launch {
repository.accounts.value?.forEach {
launch { repository.refresh(it.name) }
}
}
Timber.i("Done refreshing!")
}
I do want the refresh to update all accounts in parallel, this is why I am using launch. I have also decided to do this in the ViewModel, rather than in the repository, since that would have required to launch a new coroutine in the repository. Which per this post is discouraged since repositories don't have a natural lifecycle.
The above function, refresh, is invoked from the UI and shows a refresh-indicator while the RecyclerView is updated. So I want to stop this indicator once all accounts have been updated.
My code as shown above doesn't do this, since it will launch all the updates and then print the log statement before all updates have been finished. As a result the refresh-indicator disappears although there are still updates.
So my question (finally) is: how can I refactor the code so that it runs all updates in parallel, but makes sure refresh doesn't return before all of them have finished?
EDIT #1
Going back to what I want to achieve: showing the refresh-indicator while the view is updating, I came up with the following (changed the refresh function in the ViewModel):
fun refresh() {
viewModelScope.launch {
try {
coroutineScope {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}
}
} catch (cause: CancellationException) {
throw cause
} catch (cause: Exception) {
Timber.e(cause)
} finally {
_refreshing.value = false
}
}
}
The ViewModel exposes a LiveData for when it is refreshing and the fragment can observe it to show or hide the spinner. This seems to do the trick. However, it still doesn't feel right and I appreciate any improved solutions.
In order to await for all of your parallel refresh() operations, simply use awaitAll():
coroutineScope.launch {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}.awaitAll()
_refreshing.value = false
}
Furthermore, It's not advised to wrap coroutines with try/catch.
You can read more on this here.

Kotlin Coroutine to escape callback hell

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.

RxJava Subject: Listen to different type that the one it emits

I've been poking around with chaining Rx events and am having a bit of trouble getting what I want to work.
I'm basically requesting a user permission, requesting the user's location if it's been granted and then requesting data from an API. The code below is from a ViewModel and I want my View (technically a Fragment) to listen. My issue with the code below is that (obviously), my first flatmap isn't getting called as no initial event is triggering my Subject. I've been trying a few variants of this code but can't find the right syntax for what I'm trying to achieve.
Any help would be greatly appreciated.
var dataSubject = PublishSubject.create<Array<Data>>()
fun getData() : Observable<Array<Data>> {
dataSubject
.flatMap { permissions.request(Manifest.permission.ACCESS_COARSE_LOCATION) }
.flatMap { granted: Boolean ->
if (granted) {
locationProvider.lastKnownLocation
} else {
throw Error()
}
}
.flatMap { location: Location ->
dataRepository.getData(location.latitude, location.longitude)
}
return dataSubject
}

Categories

Resources