Calling asyn method in synchronous way in Kotlin coroutine - android

I am trying to run a Google ML Kit function and the result will be in callback and need to pass that value as a return type for the method in which it was executing in Kotlin. I tried some of the samples of Kotlin coroutines but still I am missing something and it was failing. I am still learning Kotlin.
internal fun processImageSync(image: InputImage) : String{
var doctype = ""
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
recognizer.process(image)
.addOnSuccessListener { visionText ->
var texttoscan = visionText.text.trim()
doctype = findstr(texttoscan)
}
.addOnFailureListener {
}
return doctype;
}
How can I solve the issue?

Use kotlinx-coroutines-play-services module
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0")
}
Use extension Task.await
internal suspend fun processImageSync(image: InputImage): String {
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
return recognizer.process(image)
.await()
.let { visionText -> findstr(visionText.text.trim()) }
}

Related

How to do parallel network requests in the repository ? MVVM

I am working on an Android project and at the moment we are doing multiple network calls in a single repository, for example in the PostsRepository class there are multiple endpoints that needs to be called e.g. (/getNewspost /getPostPrice and maybe /get) then it returns a large Post data class back to the ViewModel.
Although it seems fine, the downside of this structure is being unable to do parallel network calls in the repository like the features of launch, or async/await which only exists in the ViewModel.
So question is can this logic be moved to the ViewModel so then i can do multiple network calls ? Or if this logic should stay in the repository how can we do parallel calls in the repo?
You can create coroutine in Repository class also.
Class PostsRepository{
suspend fun callAPIs() : String{
return withContext(Dispatchers.IO) {
val a = async { getPost() }
val b = async { getNews() }
return#withContext a.await() + b.await()
}
}
}
With Clean architecture , you can create a UseCase to handle this behavior
1.first way
Class GetPostsUseCase(private val postRepository : PostRepository){
suspend operator fun invoke():List<Post>{
// we assume that getPosts()
// and getPostsPricies() are also suspend functions
val posts = postRepository.getPosts()
val prices = postRepository.getPostPricies()
return build(posts , prices)
}
private fun build(posts,prices) :List<Post>{
// build your data object here
}
}
/////// OR ////////
Class GetPostsUseCase(private val postRepository : PostRepository){
suspend operator fun invoke():List<Post> = withContext(Dispatchers.IO){
val posts = async{postRepository.getPosts()}
val prices = async { postRepository.getPostPricies() }
posts.await()
prices.await()
return build(posts, prices)
}
private fun build(posts,prices) :List<Post>{
// build your data object here
}
}
You can achieve this by using suspend and withContext
class PostsRepository {
suspend fun fetchPostData(): Post {
return withContext(Dispatchers.IO) {
val fetchA = async { getA() }
val fetchB = async { getB() }
val fetchC = async { getC() }
//More if needed ...
//Then execute waitAll() to get them all as parallel
val (AResult, BResult, CResult) = awaitAll(fetchA, fetchB, fetchC)
//Finally use the result of these fetch when all of them is completed
return#withContext Post(AResult, BResult, CResult)
}
}
}

What would be the proper way to get an instance of realm (kotlin sdk, version 0.10.0)? Alternative to Realm.getDefaultInstance() on Java SDK?

I initialized realm in my Application classe like this:
val realmConfig = RealmConfiguration.Builder(schema =
setOf(User::class)
) .name("myrealm_DB.db")
.schemaVersion(1)
.deleteRealmIfMigrationNeeded()
.log(LogLevel.ALL)
.build()
realm = Realm.open(configuration = realmConfig)
What would be the proper way to get an instance of realm in another activity? Since in the kotlin sdk we dont have Realm.getDefaultInstance() method? Is there a way to create like a global RealmManager class?
Since it's very specific to each pattern, and model that you used while developing application and enterprise models there is not a basic answer, unfortunately. Still, I'll share one pattern I used to do while working with Realm.
object RealmProcessor {
private var realmInstance: Realm? = nil
fun startRealm(someSpecialProperties: <Type>, completion: () -> Unit? = {}){
runInSafeQueue ({
try {
val config = RealmConfiguration.Builder(
setOf(
User::class,
Message::class,
MessageEmbed::class,
MessageViewer::class,
MessageRecipient::class,
Inbox::class,
InboxUser::class,
Sync::class
)
)
config.schemaVersion(1)
config.deleteRealmIfMigrationNeeded()
// We're using also Realm-JS, since we want the same directory that the JS thread created.
config.name("my-percious-realm-$my_custom_property.realm")
realmInstance = Realm.open(config.build())
} catch(e: Error) {
logError("Realm start error", thrown = e)
}
})
}
// Since the threads has to be same for write operations which we used for opening Realm making it singleton with one dispatcher.
private fun runInSafeQueue(runner: suspend () -> Unit?, didCatch: (Error) -> Unit = { _ -> }) {
GlobalScope.launch {
try {
runner()
} catch (e: Error) {
didCatch(e)
}
}
}
// This is very basic example with making this Object class a generic Realm accessor so you initialize it in very first activity that your app used you can easily keep accessing it from any activity
inline fun <reified T: BaseRealmObject>getFromRealm(id: Int): RealmResults<T>? {
return realmInstance?.query(T::class, "id == $0", id)?.find()
}
fun <T: RealmObject>createInRealm(objectToCopyRealm: T) {
runInSafeQueue({
realmInstance?.write {
copyToRealm(objectToCopyRealm)
null
}
})
}
fun changeUserValue(changedValue: Int) {
runInSafeQueue({
realmInstance?.write {
val objectToChange = getFromRealm<User>(20)
objectToChange?.first()?.personalMessageRoom = changedValue
}
})
}
}
Hope helps anyone they looking for some point for the start

How to properly use firebase-firestore async calls

I am working on my Final Year Project and I am really stuck on the decision should I use callbacks or coroutines of Kotlin. I created separate Module for the firebase where all its operations are done there weather its data retrieval or any other functionalities.
the problem is that whenever I return the user from the function it return null due than I understand it due to the async calls and after that I used call back for it like this:
fun getUserAsModel(callback: (User) -> Unit) {
FirebaseAuth.getInstance().uid?.let {
firestore.collection(Constants.FireCollections.USERS)
.document(it)
.get()
.addOnSuccessListener { it1 ->
val user = it1.toObject(User::class.java)?.let { it2 ->
callback(it2)
}
}
.addOnFailureListener {
Log.e(TAG, "In userModel()->", it)
it.stackTrace
}
}
}
But I see in many forms that I should I use coroutines and now I am using this approach but it does not work:
fun getUser () : User? {
var user:User? = null
val collection = firestore.collection(Constants.FireCollections.USERS)
val document = collection.document(FirebaseAuthRepository().getCurrentUserId())
try {
scope.launch {
val snapshot = document.get().await()
user = snapshot.toObject(User::class.java)
}
} catch (e:FirebaseFirestoreException) {
Log.e(TAG, "In getUser() -> " ,e)
e.stackTrace
}
return user
}
I am still stuck because every time I use getUser() I need to launch the scope of coroutines and this is really makes the code juncky.
I would like to know about your solution how should I properly implement this. Thanks
You're recreating the same problem you had with the asynchronous call, since a coroutine is launched asynchronously. The correct way to do it with a coroutine is to make it a suspend function and directly return the user without launching another coroutine inside this function.
The function should look like this:
suspend fun getUser () : User? {
val collection = firestore.collection(Constants.FireCollections.USERS)
val document = collection.document(FirebaseAuthRepository().getCurrentUserId())
return try {
val snapshot = document.get().await()
snapshot.toObject(User::class.java)
} catch (e: FirebaseFirestoreException) {
Log.e(TAG, "In getUser() -> ", e)
null
}
}
Callbacks versus coroutines is a matter of preference. Coroutines are not trivial to learn, but once you do, your code will be cleaner-looking and easier to follow.
You can also use callbackflow
fun getUserAsModel():Flow<User?> {
return callbackFlow {
FirebaseAuth.getInstance().uid?.let {
firestore.collection(Constants.FireCollections.USERS)
.document(it)
.get()
.addOnSuccessListener { it1 ->
val user = it1.toObject(User::class.java)
trySend(user)
}
.addOnFailureListener {
Log.e(TAG, "In userModel()->", it)
it.stackTrace
cancel(it)
}
awaitClose { close() }
}
}
}

Kotlin Flow: emitAll is never collected

I am trying to write a UnitTest for the kotlin-version of networkBoundResource that can be found on serveral sources with several features
Here is my version of it with marker-comments for the following question.
inline fun <ResultType, RequestType> networkBoundResource(
...
coroutineDispatcher: CoroutineDispatcher
) = flow {
emit(Resource.loading(null)) // emit works!
val data = queryDatabase().firstOrNull()
val flow = if (shouldFetch(data)) {
emit(Resource.loading(data)) // emit works!
try {
saveFetchResult(fetch())
query().map { Resource.success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.error(throwable.toString(), it) }
}
} else {
query().map { Resource.success(it) }
}
emitAll(flow) // emitAll does not work!
}.catch { exception ->
emit(Resource.error("An error occurred while fetching data! $exception", null))
}.flowOn(coroutineDispatcher)
This is one of my UnitTests for this code. The code is edited a bit to focus on my question:
#get:Rule
val testCoroutineRule = TestCoroutineRule()
private val coroutineDispatcher = TestCoroutineDispatcher()
#Test
fun networkBoundResource_noCachedData_shouldMakeNetworkCallAndStoreUserInDatabase() = testCoroutineRule.runBlockingTest {
...
// When getAuthToken is called
val result = networkBoundResource(..., coroutineDispatcher).toList()
result.forEach {
println(it)
}
}
The problem is that println(it) is only printing the Resource.loading(null) emissions. But if you have a look at the last line of the flow {} block, you will see that there should be another emission of the val flow. But this emission never arrives in my UnitTest. Why?
I'm not too sure of the complete behaviour, but essentially you want to get a resource, and current flow is all lumped into the FlowCollector<T> which makes it harder to reason and test.
I have never used or seen the Google code before and if I'm honest only glanced at it. My main take away was it had poor encapsulation and seems to break separations of concern - it manages the resource state, and handles all io work one one class. I'd prefer to have 2 different classes to separate that logic and allows for easier testing.
As simple pseudo code I would do something like this :
class ResourceRepository {
suspend fun get(r : Request) : Resource {
// abstract implementation details network request and io
// - this function should only fulfill the request
// can now be mocked for testing
delay(3_000)
return Resource.success(Any())
}
}
data class Request(val a : String)
sealed class Resource {
companion object {
val loading : Resource get() = Loading
fun success(a : Any) : Resource = Success(a)
fun error(t: Throwable) : Resource = Error(t)
}
object Loading : Resource()
data class Success(val a : Any) : Resource()
data class Error(val t : Throwable) : Resource()
}
fun resourceFromRequest(r : Request) : Flow<Resource> =
flow { emit(resourceRepository.get(r)) }
.onStart { emit(Resource.loading) }
.catch { emit(Resource.error(it)) }
This allows you to massively simplify the actual testing of the resourceFromRequest() function as you only have to mock the repository and one method. This allows you to abstract and deal with the networking and io work elsewhere, independently which again can be tested in isolation.
As #MarkKeen suggested, I now created my own implementation and it works quite well. Compared to the code that is going around on SO, this version now injects the coroutineDispatcher for easier testing, it lets flow take care of error handling, it does not contain nested flows and is imho easier to read and understand, too. There is still the side-effect of storing updated data to the database, but I am too tired now to tackle this.
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.*
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType?>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline shouldFetch: (ResultType?) -> Boolean = { true },
coroutineDispatcher: CoroutineDispatcher
) = flow<Resource<ResultType>> {
// check for data in database
val data = query().firstOrNull()
if (data != null) {
// data is not null -> update loading status
emit(Resource.loading(data))
}
if (shouldFetch(data)) {
// Need to fetch data -> call backend
val fetchResult = fetch()
// got data from backend, store it in database
saveFetchResult(fetchResult)
}
// load updated data from database (must not return null anymore)
val updatedData = query().first()
// emit updated data
emit(Resource.success(updatedData))
}.onStart {
emit(Resource.loading(null))
}.catch { exception ->
emit(Resource.error("An error occurred while fetching data! $exception", null))
}.flowOn(coroutineDispatcher)
One possible UnitTest for this inline fun, which is used in an AuthRepsitory:
#ExperimentalCoroutinesApi
class AuthRepositoryTest {
companion object {
const val FAKE_ID_TOKEN = "FAkE_ID_TOKEN"
}
#get:Rule
val testCoroutineRule = TestCoroutineRule()
private val coroutineDispatcher = TestCoroutineDispatcher()
private val userDaoFake = spyk<UserDaoFake>()
private val mockApiService = mockk<MyApi>()
private val sut = AuthRepository(
userDaoFake, mockApiService, coroutineDispatcher
)
#Before
fun beforeEachTest() {
userDaoFake.clear()
}
#Test
fun getAuthToken_noCachedData_shouldMakeNetworkCallAndStoreUserInDatabase() = testCoroutineRule.runBlockingTest {
// Given an empty database
coEvery { mockApiService.getUser(any()) } returns NetworkResponse.Success(UserFakes.getNetworkUser(), null, HttpURLConnection.HTTP_OK)
// When getAuthToken is called
val result = sut.getAuthToken(FAKE_ID_TOKEN).toList()
coVerifyOrder {
// Then first try to fetch data from the DB
userDaoFake.get()
// Then fetch the User from the API
mockApiService.getUser(FAKE_ID_TOKEN)
// Then insert the user into the DB
userDaoFake.insert(any())
// Finally return the inserted user from the DB
userDaoFake.get()
}
assertThat(result).containsExactly(
Resource.loading(null),
Resource.success(UserFakes.getAppUser())
).inOrder()
}
}

Use property as accessor for Kotlin Coroutine

Kotlin Coroutines question... struggling w/ using a property instead of a function being the accessor for an asynchronous call.
Background is that I am trying to use the FusedLocationProviderClient with the kotlinx-coroutines-play-services library in order to use the .await() method on the Task instead of adding callbacks...
Currently having a property getter kick out to a suspend function, but not sure on how to launch the coroutine properly in order to avoid the
required Unit found XYZ
error...
val lastUserLatLng: LatLng?
get() {
val location = lastUserLocation
return if (location != null) {
LatLng(location.latitude, location.longitude)
} else {
null
}
}
val lastUserLocation: Location?
get() {
GlobalScope.launch {
return#launch getLastUserLocationAsync() <--- ERROR HERE
}
}
private suspend fun getLastUserLocationAsync() : Location? = withContext(Dispatchers.Main) {
return#withContext if (enabled) fusedLocationClient.lastLocation.await() else null
}
Any thoughts on how to handle this?
Properties can't be asynchronous. In general you should not synchronize asynchronous calls. You'd have to return a Deferred and call await() on it when you need a value.
val lastUserLatLng: Deferredd<LatLng?>
get() = GlobalScope.async {
lastUserLocation.await()?.run {
LatLng(latitude, longitude)
}
}
val lastUserLocation: Deferred<Location?>
get() = GlobalScope.async {
getLastUserLocationAsync()
}
private suspend fun getLastUserLocationAsync() : Location? = withContext(Dispatchers.Main) {
return#withContext if (enabled) fusedLocationClient.lastLocation.await() else null
}
But technically it's possible, though you should not do it. runBlocking() blocks until a value is available and returns it.

Categories

Resources