Make manual shorebird update actually work

Restarting just the activity doesn't seem to work.
To work around this, I finished the process and started the activity via alarm manager.
This is inspired from ProcessPhoenix
This commit is contained in:
Gergely Hegedus 2025-01-17 21:54:22 +02:00
parent 2519513638
commit 20c693004a
5 changed files with 123 additions and 28 deletions

View file

@ -17,6 +17,13 @@
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"

View file

@ -1,5 +1,55 @@
package org.fnives.flutter.experiment_shorebird package org.fnives.flutter.experiment_shorebird
import android.app.Activity
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity()
class MainActivity : FlutterActivity() {
private val channel = "MainChannel"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
channel
).setMethodCallHandler { call, result ->
when (call.method) {
Methods.RESTART.methodName -> {
val pendingIntent = PendingIntent.getActivity(
context,
4201,
if (isHomeApp()) Intent(intent.action).apply {
flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
intent.categories.forEach {
addCategory(it)
}
} else Intent(this, this::class.java).apply {
flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
},
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
alarmManager[AlarmManager.RTC, System.currentTimeMillis() + 100] =
pendingIntent
if (context is Activity) {
(context as Activity).finishAndRemoveTask()
}
Runtime.getRuntime().exit(0)
result.success(Unit)
}
}
}
}
private fun isHomeApp(): Boolean {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
val res = packageManager.resolveActivity(intent, 0)
return res?.activityInfo != null && (packageName == res.activityInfo.packageName)
}
}

View file

@ -0,0 +1,7 @@
package org.fnives.flutter.experiment_shorebird
enum class Methods(val methodName: String) {
RESTART("restart");
}

View file

@ -1,15 +1,16 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shorebird_code_push/shorebird_code_push.dart'; import 'package:shorebird_code_push/shorebird_code_push.dart';
import 'package:restart_app/restart_app.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
} }
class ShorebirdPatch extends StatefulWidget { class ShorebirdPatch extends StatefulWidget {
const ShorebirdPatch({super.key});
@override @override
State<StatefulWidget> createState() => _ShorebirdPatchState(); State<StatefulWidget> createState() => _ShorebirdPatchState();
} }
@ -20,39 +21,65 @@ enum PatchState {
updateReady; updateReady;
} }
enum MainChannelMethods {
restart(methodName: "restart");
final String methodName;
const MainChannelMethods({required this.methodName});
}
class _ShorebirdPatchState extends State<ShorebirdPatch> { class _ShorebirdPatchState extends State<ShorebirdPatch> {
final shorebird = ShorebirdCodePush(); final shorebird = ShorebirdCodePush();
late Timer timer; late Timer timer;
PatchState patchState = PatchState.noUpdate; PatchState patchState = PatchState.noUpdate;
final methodChannel = const MethodChannel("MainChannel");
@override @override
void initState() { void initState() {
timer = Timer.periodic(const Duration(minutes: 5), (timer) async { timer = Timer.periodic(const Duration(minutes: 1), (timer) async {
if (!shorebird.isShorebirdAvailable()) { checkForPatch(timer);
timer.cancel();
return;
}
if (!await shorebird.isNewPatchAvailableForDownload()) {
setState(() {
patchState = PatchState.noUpdate;
});
return;
}
final download = shorebird.downloadUpdateIfAvailable();
setState(() {
patchState = PatchState.loading;
});
await download;
if (await shorebird.isNewPatchReadyToInstall()) {
setState(() {
patchState = PatchState.updateReady;
});
return;
}
}); });
checkForPatch(timer);
super.initState(); super.initState();
} }
Future<void> checkForPatch(Timer timer) async {
print('timer activated');
if (await shorebird.isNewPatchReadyToInstall()) {
print('timer await shorebird.isNewPatchReadyToInstall()');
setState(() {
patchState = PatchState.updateReady;
});
return;
}
if (!shorebird.isShorebirdAvailable()) {
print('timer !shorebird.isShorebirdAvailable()');
timer.cancel();
return;
}
if (!await shorebird.isNewPatchAvailableForDownload()) {
print('timer !await shorebird.isNewPatchAvailableForDownload()');
setState(() {
patchState = PatchState.noUpdate;
});
return;
}
final download = shorebird.downloadUpdateIfAvailable();
setState(() {
patchState = PatchState.loading;
});
await download;
if (await shorebird.isNewPatchReadyToInstall()) {
print('timer await shorebird.isNewPatchReadyToInstall()');
setState(() {
patchState = PatchState.updateReady;
});
return;
}
print('timer end');
}
@override @override
void dispose() { void dispose() {
timer.cancel(); timer.cancel();
@ -70,7 +97,7 @@ class _ShorebirdPatchState extends State<ShorebirdPatch> {
return InkWell( return InkWell(
child: const SizedBox.square(dimension: 48, child: Icon(Icons.update, size: 24)), child: const SizedBox.square(dimension: 48, child: Icon(Icons.update, size: 24)),
onTap: () { onTap: () {
Restart.restartApp(); methodChannel.invokeMethod(MainChannelMethods.restart.methodName);
}, },
); );
} }
@ -117,12 +144,16 @@ class _MyHomePageState extends State<MyHomePage> {
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title), title: Text(widget.title),
actions: [ShorebirdPatch()], actions: const [ShorebirdPatch()],
), ),
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Text(
'This is a release 1.9.1',
// 'You have times:',
),
const Text( const Text(
'You have pushed the button this many times:', 'You have pushed the button this many times:',
// 'You have times:', // 'You have times:',

View file

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.1+1 version: 1.9.1+191
environment: environment:
sdk: '>=3.4.3 <4.0.0' sdk: '>=3.4.3 <4.0.0'