Skip to content

Commit

Permalink
Improve Stability of Calendar Widget on Android (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobkoerber authored Apr 10, 2024
1 parent b0d6cde commit 3544f24
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 34 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ flutter {

dependencies {
implementation 'com.android.support:multidex'
implementation 'joda-time:joda-time:2.12.6'
implementation 'joda-time:joda-time:2.12.7'
def appcompat_version = "1.6.1"
implementation("androidx.appcompat:appcompat:$appcompat_version")
implementation("androidx.appcompat:appcompat-resources:$appcompat_version")
Expand Down
38 changes: 38 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
# joda
-keep class org.joda.** {*;}
-dontwarn org.joda.**

# prettytime
-keep class org.ocpsoft.pretty.time.i18n.**
-keep class org.ocpsoft.prettytime.i18n.**
-keep class org.ocpsoft.prettytime.units.**
-keepnames class ** implements org.ocpsoft.prettytime.TimeUnit

# gson
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-if class *
-keepclasseswithmembers class <1> {
<init>(...);
@com.google.gson.annotations.SerializedName <fields>;
}
-keepnames class com.fasterxml.jackson.databind.** { *; }
-dontwarn com.fasterxml.jackson.databind.**

# entires recommended by Android Studio
-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication
-dontwarn com.google.android.play.core.splitinstall.SplitInstallManager
-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest$Builder
-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest
-dontwarn com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
-dontwarn com.google.android.play.core.tasks.OnFailureListener
-dontwarn com.google.android.play.core.tasks.OnSuccessListener
-dontwarn com.google.android.play.core.tasks.Task
-dontwarn com.google.api.client.http.GenericUrl
-dontwarn com.google.api.client.http.HttpHeaders
-dontwarn com.google.api.client.http.HttpRequest
-dontwarn com.google.api.client.http.HttpRequestFactory
-dontwarn com.google.api.client.http.HttpResponse
-dontwarn com.google.api.client.http.HttpTransport
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.lang.reflect.Type

class LocalDateTimeDeserializer : JsonDeserializer<DateTime> {
class LocalDateTimeDeserializer : JsonDeserializer<DateTime?> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): DateTime {
): DateTime? {
return deserializeStringToDate(json?.asString)
}
}

fun deserializeStringToDate(dateString: String?): DateTime {
val formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")
return DateTime.parse(dateString, formatter)
fun deserializeStringToDate(dateString: String?): DateTime? {
return try {
val formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")
DateTime.parse(dateString, formatter)
} catch (_: Exception) {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ private fun updateAppWidget(
val p = PrettyTime()
val widgetData = HomeWidgetPlugin.getData(context)
val lastSaved = widgetData.getString("calendar_save", null)
val lastSavedDate = deserializeStringToDate(lastSaved).toLocalDate()
val lastSavedDateString = p.format( deserializeStringToDate(lastSaved).toDate())
val lastSavedDate = deserializeStringToDate(lastSaved)?.toLocalDate()
val lastSavedDateString = p.format( deserializeStringToDate(lastSaved)?.toDate())
remoteViews.setTextViewText(R.id.calendar_widget_updated_on, lastSavedDateString)

val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
Expand Down
28 changes: 7 additions & 21 deletions lib/base/networking/cache/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,7 @@ class Cache {

Cache({required this.isar});

void add(String body, Uri uri) {
final today = DateTime.now();
final cacheEntry = CacheEntry(
id: fastHash(uri.toString()),
url: uri.toString(),
validUntil: today.add(ttl),
saved: today,
data: body,
);
isar.writeTxn(
() => isar.cacheEntrys.put(cacheEntry),
);
}

void addString(String body, String uri) {
void add(String body, String uri) {
final today = DateTime.now();
final cacheEntry = CacheEntry(
id: fastHash(uri),
Expand All @@ -37,31 +23,31 @@ class Cache {
);
}

CacheEntry? get(Uri uri) {
final hash = fastHash(uri.toString());
CacheEntry? get(String uri) {
final hash = fastHash(uri);
final entry = isar.txnSync(() => isar.cacheEntrys.getSync(hash));
if (entry != null) {
final today = DateTime.now();
if (entry.validUntil.isAfter(today)) {
return entry;
} else {
isar.writeTxnSync(() => isar.cacheEntrys.deleteSync(hash));
isar.writeTxn(() => isar.cacheEntrys.delete(hash));
return null;
}
} else {
return null;
}
}

CacheEntry? getWithString(String uri) {
Future<CacheEntry?> getAsync(String uri) async {
final hash = fastHash(uri);
final entry = isar.txnSync(() => isar.cacheEntrys.getSync(hash));
final entry = await isar.txn(() => isar.cacheEntrys.get(hash));
if (entry != null) {
final today = DateTime.now();
if (entry.validUntil.isAfter(today)) {
return entry;
} else {
isar.writeTxnSync(() => isar.cacheEntrys.deleteSync(hash));
isar.writeTxn(() => isar.cacheEntrys.delete(hash));
return null;
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions lib/base/networking/cache/grpc_cache_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class GrpcCacheInterceptor implements ClientInterceptor {
ClientUnaryInvoker<Q, R> invoker,
) {
final key = method.path;
final cachedResponse = cache.getWithString(key);
final cachedResponse = cache.get(key);
final factory = _getFactory<R>();
if (cachedResponse != null && factory != null) {
final data = factory(cachedResponse.data);
Expand All @@ -47,7 +47,7 @@ class GrpcCacheInterceptor implements ClientInterceptor {
/// If not found in cache, invoke the actual RPC and cache the response
final response = invoker(method, request, options);
response.then((data) {
cache.addString((data as GeneratedMessage).writeToJson(), key);
cache.add((data as GeneratedMessage).writeToJson(), key);
});

return response;
Expand Down
6 changes: 3 additions & 3 deletions lib/base/networking/cache/rest_cache_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class RestCacheInterceptor implements Interceptor {
if (options.extra["forcedRefresh"] == "true") {
cache.delete(key);
} else {
final cacheEntry = cache.getWithString(key);
final cacheEntry = await cache.getAsync(key);

/// device is online, fetch every 10 minutes
if (cacheEntry != null &&
Expand All @@ -54,7 +54,7 @@ class RestCacheInterceptor implements Interceptor {
if (options.extra["forcedRefresh"] == "true") {
cache.delete(key);
} else {
final cacheEntry = cache.getWithString(key);
final cacheEntry = await cache.getAsync(key);
if (cacheEntry != null &&
DateTime.now().difference(cacheEntry.saved).inDays <= 30) {
return handler.resolve(
Expand All @@ -78,7 +78,7 @@ class RestCacheInterceptor implements Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
final key = response.realUri.toString();
cache.addString(response.data, key);
cache.add(response.data, key);
response.extra = response.extra..addAll({"saved": DateTime.now()});
handler.next(response);
}
Expand Down

0 comments on commit 3544f24

Please sign in to comment.