I am rather new to android development and am trying to get the Advertising ID for a particular device. I've found some stack overflow suggestions on how to do so:
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext())
val adId = adInfo?.id
in a worker thread). However this keeps on giving me the "Timed out waiting for the service connection" error.
Others seems to have suggest adding the following gradle dependencies help, but not for me:
implementation 'com.google.android.gms:play-services-ads-identifiers:17.0.0'
implementation 'com.google.android.gms:play-services-base:17.1.0'
as well as having
apply plugin: 'com.google.gms.google-services'
What am I missing to properly get the Advertising ID?
I use Rxjava to get aid, you can use Asyntask, coroutine...
RxJava
fun getAdId() {
Observable.fromCallable { AdvertisingIdClient.getAdvertisingIdInfo(this).id }
.subscribeOn(AppScheduler().backgroundThread())
.observeOn(AppScheduler().mainThread())
.subscribe(Consumer<String> {
val adId = it
}, Consumer<Throwable> {
// nothing
})
}
Import Rxjava in app/build.gradle:
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
With coroutine of Kotlin:
GlobalScope.launch {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext())
val adId = adInfo?.id
Log.e("text", adId)
}
If someone wonder (like me) why they get
"Calling this from your main thread can lead to deadlock"
when trying to read it from using viewModelScope and a suspend function. Here is what you need to do:
Since viewModelScope uses Dispatchers.Main by default you need it will throw this error.
Option 1:
Pass context when launched
viewModelScope.launch(Dispatchers.Default){
// call your suspend function from here
}
but in this way you need to do some work when you want to do some work on main thread.
Option 2 (I prefer this):
use CoroutineContext in your suspend function
suspend fun getAdId(): String? {
return withContext(Dispatchers.Default) {
try {
AdvertisingIdClient.getAdvertisingIdInfo(context).id
} catch (exception: Exception) {
null // there still can be an exception for other reasons but not for thread issue
}
}
}
Try to use this code:
fun getAdvertisingId(context: Context, callback: (advertisingId: String?) -> Unit) {
Thread(Runnable {
try {
val info = AdvertisingIdClient.getAdvertisingIdInfo(context)
val id = info.id
callback(id)
} catch (e: Exception) {
callback(null)
}
}).start()
}
Related
I'm using Firebase authentication. In the repository I have this function:
override suspend fun signIn(): Result<Boolean> {
return try {
auth.signInAnonymously().await()
Result.Success(true)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
This function is called from within the ViewModel class:
var response by mutableStateOf<Result<Boolean>>(Result.Success(false))
private set
fun signIn() = viewModelScope.launch {
response = repository.signIn()
}
Which works fine but I was suggested to use in the ViewModel:
fun signIn() = viewModelScope.launch(Dispatchers.IO) {
response = repository.signIn()
}
To add Dispatchers.IO and inside the repository:
override suspend fun signIn(): Result<Boolean> {
return withContext(Dispatchers.IO) {
try {
auth.signInAnonymously().await()
Result.Success(true)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
}
To launch a coroutine using withContext. I and I don't understand why? I'm using Jetpack Compose.
Whoever suggested changing your code is wrong.
It is a general Kotlin coroutines convention that suspend functions never need to be called on a specific dispatcher, specifically because they must never block. They always internally delegate to a specific dispatcher if they need one. (But perhaps as an optimization, a private suspend function might avoid doing it for a function that must be called on the Main dispatcher.)
Since this is a convention, all the libraries from Google, Android, Square, etc. and anyone else who knows what they're doing, only have suspend functions that can be called from any dispatcher.
This includes the await() call you're using with Firebase. Therefore, your repository's signIn function is already perfectly fine as-is. Since it doesn't call any blocking functions, and the suspend function it calls is a proper suspend function that also does not block, it conforms to the standard (it doesn't block).
The function in your ViewModel is also fine. No dispatcher needs to be specified.
Actually, since you are already calling signIn from a coroutine started with Dispatchers.IO you don't have to use return withContext(...).
Since your repository method is suspend, it is able to call coroutines without special blocks like withContext.
// This line tells to launch code on separate IO thread, to avoid UI freezing
// Since default viewModelScope.launch runs on Dispatchers.Main, which is
// also used for rendering
fun signIn() = viewModelScope.launch(Dispatchers.IO) {
response = repository.signIn()
}
In your repository you can just
// Since signIn was called on IO context from viewModel, it will also
// return on IO
override suspend fun signIn(): Result<Boolean> {
return try {
auth.signInAnonymously().await()
Result.Success(true)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
We have a two ways to start coroutine async and launch.
launch will use to perform serial/sequence task in background.
async is used when we expect some result back and also want to perform parallel operation.
Same way withContext is nothing but another way of writing the async where one does not have to write await(). When withContext, is used, it runs the tasks in series instead of parallel. So one should remember that when we have a single task in the background and want to get back the result of that task, we should use withContext.
In your case you can change your code as below
fun signIn() = viewModelScope.launch(Dispatchers.IO) {
val response = async { repository.signIn()}.await()
}
and remove withContext
suspend fun signIn(): Result<Boolean> {
return try {
auth.signInAnonymously().await()
Result.Success(true)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
One more way if you don't want to use return with withContext
override suspend fun signIn() = {
withContext(Dispatchers.IO) {
try {
auth.signInAnonymously().await()
Result.Success(true)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
}
In Nutshell if you expecting some result from your task then you have to use async or withContext.
Hope I am able to solve your problem or issue.
In my sample I'm calling network operation and emitting success case but on error e.g 404 app crashes wihout emitting exception. Surrendering with try catch prevent crashes but I want to pass error till the ui layer like success case.
suspend fun execute(
params: Params,
):
Flow<Result<Type>> = withContext(Dispatchers.IO) {
flow {
emit(Result.success(run(params)))
}.catch {
emit(Result.failure(it))
}
}
There is a helpful function runCatching for creating a Result easily, but the problem in coroutines is that you don't want to be swallowing CancellationExceptions. So below, I'm using runCatchingCancellable from my answer here.
This shouldn't be a Flow since it returns a single item.
If run is a not a blocking function (it shouldn't be if you are using Retrofit with suspend functions), your code can simply be:
suspend fun execute(params: Params): Result<Type> = runCatchingCancellable {
run(params)
}
If it is a blocking function you can use:
suspend fun execute(params: Params): Result<Type> = runCatchingCancellable {
withContext(Dispatchers.IO) {
run(params)
}
}
If you were going to return a Flow (which you shouldn't for a returning a single item!!), then you shouldn't make this a suspend function, and you should catch the error inside the flow builder lambda:
fun execute(params: Params): Flow<Result<Type>> = flow {
emit(runCatchingCancellable {
run(params)
})
}
// or if run is blocking (it shouldn't be):
fun execute(params: Params): Flow<Result<Type>> = flow {
emit(runCatchingCancellable {
withContext(Dispatchers.IO) { run(params) }
})
}
If you want to use flows you can use the catch method of flows.
As you said you can use try-catch but it would break the structured concurrency since it would catch the cancellation exception as well or it would avoid the cancellation exception to be thrown.
One thing that you can do is to use an Exception handler at the point where you launch the root coroutine that calls the suspend function.
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
// handle it
}
scope.launch(handler) { // root coroutine
execute(params)
somethingThatShouldBeExecutedOnlyIfPreviousCallDoesNotThrow()
}
This solution is good for both flows and non-flow coroutines.
In the solution with the runCatching you will have to manually check the result of the first execute to avoid the second one to run.
One interesting thread is here.
Basically I have to make a network request using OkHttp in parallel to various addresses. I only care about the result of the first one that succeeds. Can I do this with Flow on Kotlin?
I've been looking around but I'm struggling with getting the requests to run in parallel, the always run in sequence.
The code basically takes a list of addresses and should return the only address that worked or null if none worked.
Thanks.
Edit: I should mention I plan on using this on Android. I can probably do it with RX but wanted to learn Flow. Also trying to limit the libraries I add to the app.
Edit: I have marked an answer as correct however that isn't how I did but it took me very close to how I did it but since I'm new to Flow I have no idea if how I did it is correct though I'm pretty sure it works after my testing.
I have a function that throws NoSuchElementException when not found. It calls searchForIPAsync which is a suspend function that does all the OkHttp work and returns true|false.
#Throws(NoSuchElementException::class)
private suspend fun findWorkingIP(ipsToTest: MutableList<String>): String? = ipsToTest
.asFlow()
.flatMapMerge(ipsToTest.size)
{ impl ->
flow<String?> {
val res = connectionHelper.searchForIPAsync(getURLToTest(impl))
if (res) {
emit(impl)
} else {
}
}
}.first()
Then I call this and catch the exception in case nothing is found:
try {
val ipFound = findWorkingIP(ipsToTest)
Log.w(TAG, "find: Got something " + ipFound);
return ipFound
} catch (ex: NoSuchElementException) {
Log.w(TAG, "find: not found");
}
Although the Flow-based solution in another answer is a close match to what you need, unfortunately as of Kotlin 1.3.2 the Flow implementation has a bug that breaks it. The bug already has a proposed fix so this should be resolved with the next patch release of Kotlin. In the meantime, here's a similar solution that uses async and Channel instead:
suspend fun getShortUrl(urls: List<String>): String = coroutineScope {
val chan = Channel<String?>()
urls.forEach { url ->
launch {
try {
fetchUrl(url)
} catch (e: Exception) {
null
}.also { chan.send(it) }
}
}
try {
(1..urls.size).forEach { _ ->
chan.receive()?.also { return#coroutineScope it }
}
throw Exception("All services failed")
} finally {
coroutineContext[Job]!!.cancelChildren()
}
}
I try to manage my threads for IO Processes. One of is for Realm usage.
like…
init {
val ai = app.packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
bundle = ai.metaData
runBlocking(appExecutors.dbContext) {
Realm.init(app)
}
}
It works fine if I set val dbContext as newSingleThreadContext(“databaseIO”)…
But I develop an android-library, so If there is an implementation of Realm on the app module, I need to set the usage on the same thread. And generally, everyone uses main thread to access to Realm. In that case I tried to set UI but it caused ANR. I can understand why it causes ANR, but I can’t find a proper solution for this scenario.
Note: if I use it with launch… it works for here. But on my RealmManager class I need to use runBlocking. So there is no way to use only launch…:slight_smile:
like…
fun getProfile(id: String): Profile? {
try {
return runBlocking(dbCoroutine) {
val query = realm!!.where(Profile::class.java).equalTo("numbers.id", id)
query.findFirst()
}
} catch (ex: Exception) {
logger.e(TAG, ex)
return null
}
}
or
internal val allProfiles: List<Profile>
get() = runBlocking(dbCoroutine) { realm!!.where(Profile::class.java).findAll() }
Is there anything I do wrong way, or any advice for better implementation?
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)