Android/Kotlin - local BroadcastReceiver is never activated by DownloadManager - kotlin

I'm trying to detect when a DownloadManager download is completed. Unfortunately, the local BroadcastReceiver I'm trying to use for that never gets called. I have seen multiple similar questions, but none solved my problem; also, if the BroadcastReceiver isn't local but declared in the manifest it DOES get called, so I don't think it's an issue with the DownloadManager.
I can't use an external BroadcastReceiver because I need to update the UI (more specifically, open another activity) when the download is completed, and as far as I know that can't be done from an external receiver (please correct me if I'm wrong there).
The DownloadManager call:
private fun download() {
val mobileNetSSDConfigRequest: DownloadManager.Request = DownloadManager.Request(
Uri.parse("https://s3.eu-west-3.amazonaws.com/gotcha-weights/weights/MobileNetSSD/MobileNetSSD.prototxt")
)
mobileNetSSDConfigRequest.setDescription("Downloading MobileNetSSD configuration")
mobileNetSSDConfigRequest.setTitle("MobileNetSSD configuration")
mobileNetSSDConfigRequest.setDestinationInExternalPublicDir(
"Android/data/giorgioghisotti.unipr.it.gotcha/files/weights/", "MobileNetSSD.prototxt")
val manager: DownloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
manager.enqueue(mobileNetSSDConfigRequest)
}
which is called upon being granted permissions:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
if (grantResults.isEmpty()
|| grantResults[0] != PackageManager.PERMISSION_GRANTED
|| grantResults[1] != PackageManager.PERMISSION_GRANTED
|| grantResults[2] != PackageManager.PERMISSION_GRANTED
|| grantResults[3] != PackageManager.PERMISSION_GRANTED
|| grantResults[4] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,
"Sorry, this app requires camera and storage access to work!",
Toast.LENGTH_LONG).show()
finish()
} else {
val mobileSSDConfig = File(sDir + mobileNetSSDConfigPath)
if (!mobileSSDConfig.exists()) download()
else {
val myIntent = Intent(this, MainMenu::class.java)
this.startActivity(myIntent)
}
}
}
}
}
The BroadcastIntent is declared like so (Splash is the name of the activity):
private val broadcastReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
val myIntent = Intent(this#Splash, MainMenu::class.java)
this#Splash.startActivity(myIntent)
}
}
}
}
and is registered in the activity's onCreate() method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LocalBroadcastManager.getInstance(this).registerReceiver(
broadcastReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.INTERNET,
android.Manifest.permission.ACCESS_NETWORK_STATE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
}
I tried registering the receiver in the onResume method and declaring it inside the onCreate method, no change. As far as I can tell, I'm doing this exactly as I saw it done in a few accepted answers and I can't see the problem. I know for a fact that the BroadcastReceiver is never called, I checked through debugging and with all sorts of console logs. The DownloadManager seems to work as intended since the file is correctly downloaded and external services are called correctly.
What am I missing?

I had a similar issue and turned out that calling simply registerReceiver(broadcastReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), without getting an instance of the LocalBroadcastManager solved the problem.
So possibly the issue was that the receiver was being registered on the wrong context object.
[remember also to take care of the un-registration of the receiver]
I did my like this
public void onResume()
{
super.onResume();
registerReceiver(broadcastReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
public void onPause()
{
super.onPause();
unregisterReceiver(broadcastReceiver);
}

Related

How to await billingClient.startConnection in Kotlin

I'm following the google billing integration instructions and have got stuck with how to await for the billing client connection result.
Whenever I need to query sku details or purchases I need to make sure that the billing client is initialized and connected. There are querySkuDetails and queryPurchasesAsync awaitable kotlin extension functions, but startConnection is based on listeners instead. Here are code samples from the docs.
private var billingClient = BillingClient.newBuilder(activity)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
suspend fun querySkuDetails() {
// prepare params
// leverage querySkuDetails Kotlin extension function
val skuDetailsResult = withContext(Dispatchers.IO) {
billingClient.querySkuDetails(params.build())
}
// Process the result.
}
How to put all this together using suspend functions?
One way to create a suspending version of startConnection is the following:
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
*/
suspend fun BillingClient.ensureReady() {
if (isReady) {
return // fast path if already connected
}
return suspendCoroutine { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingResponseCode.OK) {
cont.resume(Unit)
} else {
// you could also use a custom, more precise exception
cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
}
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
}
This will fail if another coroutine already initiated a connection.
If you want to deal with potential concurrent calls to this method, you can use a mutex to protect the connection part:
val billingConnectionMutex = Mutex()
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
* If a connection is already in the process of being established, this
* method just suspends until the billing client is ready.
*/
suspend fun BillingClient.ensureReady() {
billingConnectionMutex.withLock {
// fast path: avoid suspension if another coroutine already connected
if (isReady) {
return
}
connectOrThrow()
}
}
private suspend fun BillingClient.connectOrThrow() = suspendCoroutine<Unit> { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
cont.resume(Unit)
} else {
cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
}
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
Here, the release of the mutex corresponds to the end of connectOrThrow() for whichever other coroutine was holding it, so it's released as soon as the connection succeeds. If that other connection fails, this method will attempt the connection itself, and will succeed or fail on its own so the caller will be notified in case of error.
If you prefer to deal with result codes directly in if statements, you can return results instead of throwing:
private val billingConnectionMutex = Mutex()
private val resultAlreadyConnected = BillingResult.newBuilder()
.setResponseCode(BillingClient.BillingResponseCode.OK)
.setDebugMessage("Billing client is already connected")
.build()
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
* If a connection is already in the process of being established, this
* method just suspends until the billing client is ready.
*/
suspend fun BillingClient.connect(): BillingResult = billingConnectionMutex.withLock {
if (isReady) {
// fast path: avoid suspension if already connected
resultAlreadyConnected
} else {
unsafeConnect()
}
}
private suspend fun BillingClient.unsafeConnect() = suspendCoroutine<BillingResult> { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
cont.resume(billingResult)
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
As discussed in BillingClient.BillingClientStateListener.onBillingSetupFinished is called multiple times, it is not possible to translate startConnection into the Coroutine world, because its callback will be called multiple times.
Thanks to Joffrey's answer and comments I have managed to construct a solution with BillingResult returned
val billingConnectionMutex = Mutex()
suspend fun BillingClient.connect(): BillingResult =
billingConnectionMutex.withLock {
suspendCoroutine { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode != BillingResponseCode.OK)
Log.d(TAG, "Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})")
cont.resume(billingResult)
}
override fun onBillingServiceDisconnected() {}
})
}
}
and then call it like this within the billing flow
if(billingClient!!.connect().responseCode != BillingResponseCode.OK)
return
If billingClient is already connected it will return responseCode.OK, so no need to check for isReady.

problem with use Mvvm and Retrofit and Rxjava live data in kotlin

I'm new in kotlin and I'm trying to use retrofit with Rxjava and live data in MVVM architecture.
I config retrofit, and also use observable and subscribe in ViewModel to make observable variable to use in activity binding layout.
I have a button in my view and when I click on it, the request method gets to start and subscription write a log of its own data. but my variable gets null at first and after seconds, when retrofit completed its task, my variable gets data but log value doesn't update.
this is my retrofit initialize class
class ApiService {
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
private fun <T> callBack(iDataTransfer: IDataTransfer<T>) =
object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val data = response.body()
if (data != null)
iDataTransfer.onSuccess(data)
else
try {
throw Exception("data is empty")
} catch (ex: Exception) {
iDataTransfer.onError(ex)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
iDataTransfer.onError(t)
}
}
fun getCategories(iDataTransfer: IDataTransfer<List<Category>>) =
INSTANCE.getCategories().enqueue(callBack(iDataTransfer))
this is an interface for retrofit
#GET("GetCategories")
fun getCategories(): Call<List<Category>>
this is my model class. I think the problem is here. because the observable send null data before retrofit finish its work.
fun getCategories(): Observable<ArrayList<Category>> {
val result = arrayListOf<Category>()
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
result.addAll(data)
}
override fun onError(throwable: Throwable) {
Log.e("Model", throwable.message!!)
}
})
return Observable.just(result)
}
and this is also my ViewModel class
class ProductViewModel(private val model: ProductModel) : ViewModel() {
var isLoading = ObservableField(false)
var categoryList = MutableLiveData<ArrayList<Category>>()
private var compositeDisposable = CompositeDisposable()
fun getCategories() {
isLoading.set(true)
compositeDisposable +=
model.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
categoryList.value = it
}, {
Log.e("ViewModel", it.message.toString())
})
isLoading.set(false)
}
finally, it's my activity
lateinit var binding: ActivityMainBinding
private val vm: ProductViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.vm = vm
vm.categoryList.observe(this, Observer {
if (it != null)
Log.e("activity", it.toString())
})
}
As ExpensiveBelly mentioned in a comment, Retrofit provides a call adapter for RxJava, so you can let your API return Observable<List<Category>> directly. To do this, you will need to add the RxJava call adapter dependency to your app module's build.gradle:
implementation 'com.squareup.retrofit2:adapter-rxjava2:(version)'
Add the call adapter factory when constructing your Retrofit instance:
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // add this
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
And then just let your service return an Observable directly:
#GET("GetCategories")
fun getCategories(): Observable<List<Category>>
If ApiService needs to do some handling before the rest of the app gets the response, you can use RxJava operators like map.
It would be illustrative to see why your code doesn't work and how to fix it. When you call api.getCategories(someCallback), one of your callback methods will be called at some point in the future. In the meantime, the model.getCategories() method will return immediately.
When you subscribe to the returned Observable, it emits the result variable, which is currently an empty list. result will eventually have some data in it, but your code will not be informed of this at all.
What you really want to do is emit the list of categories when it becomes available. The standard way to get an Observable from a callback API is with Observable.create:
fun getCategories(): Observable<ArrayList<Category>> {
return Observable.create { emitter ->
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
emitter.onNext(data)
emitter.onComplete()
}
override fun onError(throwable: Throwable) {
emitter.onError(throwable)
}
})
}
}
Of course, it's better to just use RxJava2CallAdapterFactory if possible, since this work has already been done there.

Unsubscribe observer in Async grpc stub - Java / Kotlin

I have an async stub in which I added an observer:
val obs = object: StreamObserver<Hallo> {
override fun onNext(value: Hallo) {
streamSuccess(value)
}
override fun onError(t: Throwable?) {
nonSuccess(t?.message ?: "Unknow error")
}
override fun onCompleted() {
Log.d("Info", "completed")
completed()
}
}
I would like a to be able to remove this observer from the async stub, so I can cancel the streaming in the client side.
As says in the github issue: https://github.com/grpc/grpc-java/issues/3095
I tried keeping a local variable of the observer, so the client can do later on:
observer?.onError(Status.CANCELLED.cause)
That didn't work.
Also I tried to create my own class from the abstract class: ClientCallStreamObserver
class CancellableStreamObserver<TResponse>(val next:(value:TResponse)->Unit, val onError:(t:Throwable)-> Unit, val onCompleted:(()->Unit), val onCanceledHandler: (()->Unit)? = null) : ClientCallStreamObserver<TResponse>() {
override fun isReady(): Boolean {
return true
}
override fun setOnReadyHandler(onReadyHandler: Runnable?) {
//TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun disableAutoInboundFlowControl() {
//TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun cancel(message: String?, cause: Throwable?) {
//TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun request(count: Int) {
//TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun setMessageCompression(enable: Boolean) {
//TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onNext(value: TResponse) {
next(value)
}
override fun onError(t: Throwable) {
if (t is StatusException) {
if (t.status.code == Status.Code.CANCELLED) {
onCanceledHandler?.let {
it()
}
}
}
if (t is StatusRuntimeException) {
if (t.status.code == Status.Code.CANCELLED) {
onCanceledHandler?.let {
it()
}
}
}
this.onError(t)
}
override fun onCompleted() {
onCompleted()
}
}
So later on I can call:
observer?.cancel("Cancelled for the user",Status.CANCELLED.cause)
That didn't work either.
The way I know it didn't work, it's because if the user adds again a new observer, I get duplicated responses, as if the old observer is still alive.
I know I can shutdown the channel with channel.shutdownNow(). But I think it's too aggressive.
Thanks
From the referenced https://github.com/grpc/grpc-java/issues/3095:
for async you can use ClientCallStreamObserver.cancel() by casting the returned StreamObserver to ClientCallStreamObserver or implementing having your passed-in StreamObserver implement ClientResponseObserver.
(emphasis added)
grpc-java will implement the appropriate methods, not your instance. So the pattern would be:
stub.foo(req, object: ClientResponseObserver<Hallo> {
override fun beforeStart(respObs: ClientCallStreamObserver<Hallo>) {
// save respObs for later
}
override fun onNext(value: Hallo) {
streamSuccess(value)
}
override fun onError(t: Throwable?) {
nonSuccess(t?.message ?: "Unknow error")
}
override fun onCompleted() {
Log.d("Info", "completed")
completed()
}
});
// -or- (for streaming calls only)
val obs = ...;
val respObs = stub.foo(obs) as (ClientCallStreamObserver<Hallo>);
respObs.onNext(req);
// save respObs for later
Note that the respObs in both cases would be identical. Using ClientResponseObserver would mainly be for when there is streaming and want to cancel within the response observer to avoid threading races.

Kotlin coroutine immediately give an exception if last operation finished with exception

When I was try to login on my service via retrofit. When my service is off, 10 seconds after clicking the button I got an SocketTimeoutException exception.
So far everything is normal but again, I clicked the button again after the error gave the same error immediately. What's wrong?
interface LoginService {
#FormUrlEncoded
#POST("/login")
fun login(#Field("id") id: String, #Field("pw") pw: String): Deferred<Response<User>>
}
class LoginViewModel : ViewModel() {
private var job: Job = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + job)
private val service by lazy { RetrofitApiFactory().create(LoginService::class.java) }
private val excHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable);
}
fun doLogin(id: String, pw: String) {
scope.launch(excHandler) {
val response = service.login(id, pw).await()
if (response.isSuccessful) {
response.body()
?.let { user -> doOnSuccess(user) }
?: doOnError(InvalidUserException())
} else doOnError(Exception())
}
}
private fun CoroutineScope.doOnError(e: Throwable) {
excHandler.handleException(coroutineContext, e)
}
private fun doOnSuccess(user: User) {
...
}
override fun onCleared() {
job.cancel()
}
}
You need to change your CoroutineScope to not re-use the same Job. It's already considered as failed, so it will not even begin executing.
See related issue on github.

Why am I getting a NullPointerException when fetching JSON?

I keep getting a NullPointerException at return place.
When I was debugging the app, the code skips the onFailure() and onResponse() methods.
Previously, this worked but I refactored it into the current classes.
class Repository private constructor() {
private val baseUrl: String = "http://api.openweathermap.org/"
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY))
.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
val networkApi = retrofit.create(NetworkApi::class.java)
private object Holder { val INSTANCE = Repository() }
companion object {
val instance: Repository by lazy { Holder.INSTANCE }
}
fun fetchWeatherData(placeName: String): Place {
var place: Place? = null
val call: Call<Place> = networkApi.getPlaceWeather(placeName)
call.enqueue(object : Callback<Place> {
override fun onFailure(call: Call<Place>?, t: Throwable?) {
println(t?.message)
}
override fun onResponse(call: Call<Place>?, response: Response<Place>?) {
if (response != null && response.isSuccessful && response.body() != null) {
place = response.body() as Place
println(place.toString())
}
}
})
return place!!
}
}
class MainPresenter(private val view: MainContract.View, val context: Context) : MainContract.Presenter {
val repository = Repository.instance
...
override fun updateListOfPlaces() {
var places = mutableListOf<Place>()
for (index in 0 until favPlaceStrings.size) {
places.add(repository.fetchWeatherData(favPlaceStrings.elementAt(index)))
}
view.showFavouritePlaces(places)
}
}
The way you're using retrofit makes it have an asynchronous behaviour, meaning the code within onFailure and onResponse might run after or before you have a chance to return from fetchWeatherData. In other words, you cannot assume that place will have a value when you return from fetchWeatherData and this is actually what's happening, place is still null and calling !! will cause the null pointer exception you're having.
To fix this you either change the way you're using retrofit to be synchronous, or you use an approach like callbacks.
Personally, I prefer the callback approach / reactive streams and this you can check here.
Making the code synchronous will most likely lead to other issues such as network calls on the main thread, which are not allowed and crash the app.
You need to invert your logic. You cannot simply "return data" from a network call that you're waiting for
Instead, loop over the list, make requests, then show/update the view
for (index in 0 until favPlaceStrings.size) {
val call: Call<Place> = networkApi.getPlaceWeather(favPlaceStrings.elementAt(index))
call.enqueue(object : Callback<Place> {
override fun onFailure(call: Call<Place>?, t: Throwable?) {
println(t?.message)
}
override fun onResponse(call: Call<Place>?, response: Response<Place>?) {
if (response != null && response.isSuccessful && response.body() != null) {
val place: Place = response.body() as Place
places.add(place) // move this list to a field
println(place.toString())
view.showFavouritePlaces(places) // this is fine that it's inside a loop
}
}
})
}