WidgetUpdateHelper
WidgetUpdateHelper copied to clipboard
Works in debug mode, but not in release
Hi,
I'm struggling to make this work. It works in debug mode but not in production. I used the single example. I'm not sure what I'm doing wrong. I converted the code to kotlin as most of the code is written in it.
Here's the code:
SmallWidgetUpdater.kt
class SmallWidgetUpdater : WidgetUpdater() {
override fun update(context: Context, dataBundle: Bundle?, vararg ids: Int) {
//This callback will be running on background thread
val appWidgetManager = AppWidgetManager.getInstance(context)
var weatherData: WeatherMapper? = null
try {
//DB or Internet requests
// Thread.sleep(10000)
getWeather(context, getCity(context), getUnit(context))
weatherData = WeatherMapper.fromJson(getWeatherData(context))
} catch (e: InterruptedException) {
e.printStackTrace()
}
val pendingIntent: PendingIntent = Intent(context, MainActivity::class.java)
.let { intent ->
PendingIntent.getActivity(context, 0, intent, 0)
}
//make RemoteViews depends on dataBundle and update by widget ID
val remoteViews = RemoteViews(context.packageName, R.layout.current_weather).apply {}
remoteViews.setOnClickPendingIntent(R.id.city, pendingIntent)
if (weatherData?.weather != null){
remoteViews.setTextViewText(R.id.condition, weatherData.weather!![0].description)
remoteViews.setImageViewResource(R.id.icon, context.resources.getIdentifier("ic_" + weatherData.weather!![0].icon, "drawable", context.packageName))
remoteViews.setTextViewText(R.id.city, weatherData.name)
remoteViews.setTextViewText(R.id.temperature, weatherData.main?.temp.toString().substringBefore(".") + getUnitString(context))
}
for (widgetId in ids) {
appWidgetManager.updateAppWidget(widgetId, remoteViews)
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun makeNotification(context: Context): Notification {
val notifChannelID = "widget_updater"
val channelName = "widget updating"
val chan = NotificationChannel(notifChannelID, channelName, NotificationManager.IMPORTANCE_NONE)
val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(chan)
val notificationBuilder = Notification.Builder(context, notifChannelID)
return notificationBuilder.build()
}
companion object {
private fun getCity(context: Context): String {
val prefs: Prefs?
prefs = Prefs(context)
return prefs.city!!
}
private fun getWeatherData(context: Context): String {
val prefs: Prefs?
prefs = Prefs(context)
return prefs.currentWeather!!
}
private fun getLat(context: Context): Double {
val prefs: Prefs?
prefs = Prefs(context)
return prefs.lat
}
private fun getLon(context: Context): Double {
val prefs: Prefs?
prefs = Prefs(context)
return prefs.lon
}
private fun getUnitString(context: Context): String {
val prefs: Prefs?
prefs = Prefs(context)
val unit = prefs.unit
val unitString: String
unitString = when (unit) {
0.toLong() -> {
"°C"
}
1.toLong() -> {
"°F"
}
else -> {
"K"
}
}
return unitString
}
private fun getUnit(context: Context): String {
val prefs: Prefs?
prefs = Prefs(context)
val unit = prefs.unit
val unitString: String
unitString = when (unit) {
0.toLong() -> {
"/metric"
}
1.toLong() -> {
"/imperial"
}
else -> {
""
}
}
return unitString
}
private fun getWeather(context: Context, city: String, unitString: String) {
val url: URL = if (getCity(context) != "") {
URL("...")
} else {
URL("...")
}
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.get()
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val responseBody = response.body
val content = responseBody!!.string()
val prefs: Prefs?
prefs = Prefs(context)
println("content")
println(content)
prefs.currentWeather = content
}
}
})
}
}
}
SmallWidgetProvider.kt
@RemoteViewsUpdater(SmallWidgetUpdater::class)
class SmallWidgetProvider : AppWidgetProvider() {
override fun onReceive(context: Context?, intent: Intent?) {
println("updateWidgets onReceive")
super.onReceive(context, intent)
val appWidgetManager = AppWidgetManager.getInstance(context)
val widgetIds = appWidgetManager.getAppWidgetIds(
context?.let { ComponentName(it, SmallWidgetProvider::class.java) })
// update widgets
for (widgetId in widgetIds) {
WidgetUpdateService.updateWidgets(context, this, null, widgetId)
}
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
println("updateWidgets onUpdate")
// trigger you WidgetUpdater by this call
for (widgetId in appWidgetIds) {
WidgetUpdateService.updateWidgets(context, this, null, widgetId)
}
}
override fun onEnabled(context: Context) {
println("updateWidgets onEnabled")
val appWidgetManager = AppWidgetManager.getInstance(context)
val widgetIds = appWidgetManager.getAppWidgetIds(
ComponentName(context, SmallWidgetProvider::class.java))
// update widgets
for (widgetId in widgetIds) {
WidgetUpdateService.updateWidgets(context, this, null, widgetId)
}
}
override fun onDisabled(context: Context) {
println("updateWidgets onDisabled")
}
}
and added this to manifest:
<receiver android:name=".SmallWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/current_weather_info" />
</receiver>
I'm storing the data in sharedPrefs and then using it. Tried using it directly also in updater but same effect.
Any idea why is this happening?
and the appwidget-provider xml is this:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/current_weather"
android:initialLayout="@layout/current_weather"
android:minHeight="110dp"
android:minResizeHeight="40dp"
android:minWidth="110dp"
android:minResizeWidth="40dp"
android:previewImage="@drawable/current_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen" />
Hi @eboye . What's means "doesn't works in release". I see that you post a lot of sources. Is it possible to make a sample with full source that can be executable (just make a sample on GitHub for debug) ?
Hi @HeyAlex
What I mean "doesn't work in release" is that when ran from AS in debug mode it works fine. When I compile it, sign it and install it on emulator or real device it does nothing in the background. It's really hard to debug the information on why is that as adb logcat can't show much in that state.
I've isolated only the widget code here:
https://github.com/eboye/widget_test
and it actually works. I'm not sure why it works here and not in my app which I can't share the code as it contains assets and code which I'm not allowed to share.
But basically the app is done in flutter, it has the UI to either choose the current location or enter the name of the city.
Then it updates the info in the app itself, sets the shared preferences info as:
Latitude Longitude City name units of measurement
Then in widget provider/updater I'm getting that information (successfully) and then making an API call which get's the information and sets that JSON as string in sharedpreference in currentWeather field.
Than I'm using that information to update the widgets.
I'm pretty bad at kotlin/java. I'm mostly a PHP/JS developer so I'm not sure how to make async calls and use the promise to chain the functions in kotlin/java.
Basically the:
getWeather(context, getCity(context), getUnit(context))
Thread.sleep(1000) // wait for the data to be written to sharedPref
weatherData = WeatherMapper.fromJson(getWeatherData(context))
does this in updater and then after that I'm using that weatherData to update widgets.
I've even tried to place everything right there in try catch block and in onResponse update the widgets. It works in debug, but not in release. :(
It doesn't even enter the OkHttpClient call when in release for some reason.
What I managed to debug is when I remove the Thread.sleep(1000) it actually tries to make the call to the API exactly 3 times and throws an error in the console that there are some concurrent calls.
I don't know how to make it call only once. Maybe it's being called on every resize widget event or something.
I'm actually not sure of m approach here. As I see that you are suggesting that we use dataBundle to pass the info. But that part (in my mind) would not work either as I would be doing long background tasks in the provider itself to collect that information.
I'm really stuck at this. I've tried many approaches but nothing seems to work except this library which is pretty intuitive.
I'm also thinking on using the service if that's even good approach. To collect info at regular times and the the widget updater just needs to use that info to update widgets.
All the help is greatly appreciated. :D
and btw, to test it out, you'll need openweathermap apiKey and replace it here: https://github.com/eboye/widget_test/blob/master/app/src/main/java/net/nocopypaste/weatherwidgettest/SmallWidgetUpdater.kt#L130
"I'm also thinking on using the service if that's even good approach." - this library is using service, since it's only a solution if you need to update widget as soon as possible (job service or work manager is not guarantee one shoot fast updates).
Am i understand correctly that in your project sample it's works fine in release build? If yes, please check build.gradle of your main app and make sure that configuration of release build is the same as in test project. (just to be clear that moment)
I also not familiar with Flutter stack so i don't understand how it's used in Flutter project. (maybe problem on this side)
I can also confirm that this library works fine with app which is already on play store.
Anyway i will take a quick look at your project and return if i find something wrong.
@HeyAlex same thing happen with me too. It work in debug but fail with release. After trial and error I found that if we use minifyEnabled true then it doesnot work :( does it have something to do with progaurd ?
woah, it's probably problem with proguarding. can you try with custom proguard rule to keep whole classes which extends WidgetUpdater?
Something like:
-keep class ** extends heyalex.widgethelper.**
-keep class ** extends your.widgetupdater.class
if the problem is still could be reproduced, i'll take a look closely on this
@HeyAlex Thanks man, you are amazing 🥇 Works like charm :)
just add below line in your custom proguard rule file -keep class ** extends heyalex.widgethelper.**