Flutter - How to check is the app has a new version? - flutter

I create an app with Flutter. I need to force users to update the app when I upload a new version. I try this package, but I think that it doesn´t work. In the documentation, we can read that we can verify the version with this code.
Container(
margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
child: UpgradeCard();
)
The problem is: I need to check the version in *'initState', not in Scaffold. So what is the right way to check the version on the app start?

I recommend trying out the Firebase Remote Config and update the minimum required version there. It's not a good practice to force update app on every update in the store. Also, some users can see the updated version in given store later than others. This is just how Google Play and AppStore work.
Thus, when you really need to force update the application you can increment the parameter on Firebase e.g. a day after update in the store.
Simple code to trigger the dialog can look as following:
Future<bool> checkUpdates() async {
await remoteConfig.fetch();
await remoteConfig.activateFetched();
final requiredBuildNumber = remoteConfig.getInt(Platform.isAndroid
? 'requiredBuildNumberAndroid'
: 'requiredBuildNumberIOS');
final currentBuildNumber = int.parse(packageInfo.buildNumber);
return currentBuildNumber < requiredBuildNumber;
}
This requires package_info package to be added as well as firebase_remote_config.
To open the app in the store you need to use url_launcher package and just pass URL to your app.

you can call https://itunes.apple.com/lookup?bundleId=$id or https://play.google.com/store/apps/details?id=$id to receive the newest app version available on the stores.
i can recommend this package to do this: https://pub.dev/packages/new_version

You can check your app version like
#override
void initState() {
super.initState();
Timer(
Duration(seconds: 3), // put your stuff here
() => Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => LoginScreen())));}
and presume one thing this code must be in spalsh screen or home screen
and there you can check app version forcefully stop any app which is belonged old version

Try out the in app update package- https://pub.dev/packages/in_app_update which works on Android and for iOS, try- https://pub.dev/packages/upgrader.

Use this package -> new_version
It's awesome & easy :)
///initialize your variable
final newVersion = NewVersion(
iOSId: 'com.google.Vespa',
androidId: 'com.google.android.apps.cloudconsole',
);
//and use this inside of your widget or controller
#override
Widget build(BuildContext context) {
newVersion.showAlertIfNecessary(context: context);
return Container();
}

If you just want the current version number from the App Store and Play Store. You can use the upgrader package API like this:
import 'dart:developer';
import 'dart:io';
import 'package:html/dom.dart';
import 'package:upgrader/upgrader.dart';
Future<String?> getStoreVersion(String myAppBundleId) async {
String? storeVersion;
if (Platform.isAndroid) {
PlayStoreSearchAPI playStoreSearchAPI = PlayStoreSearchAPI();
Document? result = await playStoreSearchAPI.lookupById(myAppBundleId, country: 'US');
if (result != null) storeVersion = PlayStoreResults.version(result);
log('PlayStore version: $storeVersion}');
} else if (Platform.isIOS) {
ITunesSearchAPI iTunesSearchAPI = ITunesSearchAPI();
Map<dynamic, dynamic>? result =
await iTunesSearchAPI.lookupByBundleId(myAppBundleId, country: 'US');
if (result != null) storeVersion = ITunesResults.version(result);
log('AppStore version: $storeVersion}');
} else {
storeVersion = null;
}
return storeVersion;
}
EDIT: If your app is NOT available on the US App Store or Play Store, you need to change the country code.

Related

How to grant system permissions while writing flutter integration tests?

I am writing flutter integration tests https://docs.flutter.dev/cookbook/testing/integration/introduction
I can find and tap on my widgets perfectly fine but the problem arises when I have to click the system widgets to grant permissions in order to continue and test the remaining flow.
For example:
I tap a button then have to grant location permissions. At this moment the screen as below is shown
This screen is shown by the system and I have no key or way how to tap "While using the app".
Also the system screen might change between devices like ios and android and the text might be different as well.
What is the best practice to solve this issue? I am blocked from testing the remaining screens that use the required permissions..
What I tried so far?
I tried to grant the permissions manually for Android as a start but did not work
Future<void> grantRequiredAppPermissions() async {
if (!Platform.isAndroid) {
return;
}
const appPackageName = 'my.package';
print(
'We are going to manually grant the required permissions to the android package $appPackageName');
final Map<String, String> envVars = Platform.environment;
String adbPath = join(
envVars['ANDROID_SDK_ROOT'] ?? envVars['ANDROID_HOME']!,
'platform-tools',
Platform.isWindows ? 'adb.exe' : 'adb',
);
print('Using adb at $adbPath');
final permissions = [
'android.permission.READ_EXTERNAL_STORAGE',
'android.permission.WRITE_EXTERNAL_STORAGE',
'android.permission.ACCESS_FINE_LOCATION',
'android.permission.ACCESS_COARSE_LOCATION'
];
for (final permission in permissions) {
await Process.run(
adbPath, ['shell', 'pm', 'grant', appPackageName, permission]);
}
}
I was calling that function in my app/integration_test/driver.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';
import 'helpers/grant_required_permissions.dart';
Future<void> main() async {
await grantRequiredAppPermissions();
await integrationDriver();
}
but did not help at all. Also it is not a proper solution because even if it was going to work, would work only for android and i test also on ios devices.
You can try out the testing framework patrol https://pub.dev/packages/patrol
See the documentation here: https://patrol.leancode.co/
It let's you interact with the native ui like this:
await $.native.grantPermissionWhenInUse();
await $.native.grantPermissionOnlyThisTime();
await $.native.denyPermission();

Flutter Hive opens existing box, but is not reading values from it

I am having one of those programming moments where I think I am going mad so hopefully someone can help me.
I have a Flutter app that uses Hive to store data between runs. When the app initially starts, it opens a box and retrieves some information to set the saved theme for the MaterialApp. It then builds the main page for the app and retrieves a range of other options. This was working perfectly (I have a version of it on my phone that works perfectly), but it has stopped working for some reason.
When the app executes, the initial MyApp states that the Hive box is open, but it has no values in it. This is true for a call to an options class to retrieve the options data. After that call, the box suddenly does have values and I am able to retrieve and print out the keys. When the app then builds the main page, it states that the box is open and it has values and is able to retrieve the options data from the options class. Previously, I have had no problems with the first reading of data to extract the theme. I have posted the relevant sections of code below long with the print output from a run.
I am running the app in web and have also run it on a mobile emulator. It has previously worked fine on both platforms, but is now not working on the web platform. It appears to be working fine on the mobile emulator.
The app is using the following versions:
Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision c860cba910 (6 days ago) • 2022-03-25 00:23:12 -0500
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2
pubspec.yaml dependencies:
hive: ^2.0.6
hive_flutter: ^1.1.0
I have upgraded to the latest version of Flutter today to see if that fixed the problem. I had the same issue on the previous stable release.
I have updated to hive 2.1.0 and get the same problem/output.
I have also tried downgrading Flutter to 2.10.0 with Dart 2.16.0, which I know worked fine, and that hasn't solved the problem.
main.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'package:lettercloud/data/colours.dart';
import 'package:lettercloud/options/option_page.dart';
const String _boxName = 'lettercloud';
void main() async {
await Hive.initFlutter();
Hive
..registerAdapter(CellAdapter())
..registerAdapter(ThemeModeOptionAdapter());
await Hive.openBox(_boxName);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final Box _box = Hive.box(_boxName); // Object for hive data access
final Options _options = Options();
final Colours _colours = Colours();
late bool _firstRun = true; // Flag to only read Hive options on first run
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
print('build() Before first run. Extracting box keys. Attempt 1...');
for (String key in _box.keys) {
print('box keys: $key');
}
if (_firstRun) {
print(
'First run. Hive box is open: ${_box.isOpen} Box has values: ${_box.isNotEmpty}');
_options.setHiveBox(_box); // Pass hive object and retrieve options
_firstRun = false;
}
print('');
print('build() After first run. Extracting box keys. Attempt 2...');
for (String key in _box.keys) {
print('box keys: $key');
}
return AnimatedBuilder(
animation: _options.getThemeNotifier(),
builder: (context, child) {
return MaterialApp(
title: 'Lettercloud',
theme: FlexThemeData.light(scheme: FlexScheme.jungle),
darkTheme: FlexThemeData.dark(scheme: FlexScheme.jungle),
themeMode: _options.getThemeMode(),
home: ResponsiveSizer(
builder: (context, orientation, screenType) {
return const MyPage(title: 'Lettercloud Anagram Helper');
},
),
);
});
}
}
class MyPage extends StatefulWidget {
const MyPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyPage> createState() => MyPageState();
}
class MyPageState extends State<MyPage> {
final Options _options = Options();
late final Box _box; // Object for hive data access
late Widget _displayGrid;
#override
void initState() {
super.initState();
print('Doing init MyPageState');
_box = Hive.box(_boxName);
_options.setHiveBox(_box); // Pass hive object and retrieve options
_setGrid(_options.getGridType());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lettercloud'),
),
resizeToAvoidBottomInset: false, // Overlay on-screen keyboard
body: SafeArea(
child: _displayGrid,
),
);
}
// Set the grid to display based on the grid type option
void _setGrid(GridType type) {
_displayGrid = _options.getGridType() == GridType.square
? GridSquare(box: _box, options: _options, update: updateGrid)
: GridDiamond(box: _box, options: _options, update: updateGrid);
}
// Callback to set the grid type if the option changes
void updateGrid(GridType type) {
setState(() {
_setGrid(type);
});
}
}
options.dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:hive_flutter/hive_flutter.dart';
class Options {
bool _lightMode = true; // Use light colours, or dark
static const String _lightModeName = 'lightMode';
bool _showGrid = true; // Show grid around tiles, or not
static const String _showGridName = 'showGrid';
bool _firstEdit =
true; // Flag to show edit on first start, doesn't need saving
bool _editOnStart = false; // Show edit at startup, or not
static const String _editOnStartName = 'editOnStart';
CharType _charType = CharType.mixed; // Type of letters to show
static const String _charTypeName = 'charType';
ThemeModeOption _themeMode = ThemeModeOption()..setMode(ThemeMode.light);
static const String _themeModeName = 'themeMode';
late Box _box; // Hive object
late final double _tabletF; // Reduction factor for tablet displays
late GridType _gridType = GridType.square;
static const String _gridTypeName = 'gridType';
late GridType _savedGridType = _gridType;
static const String _savedGridTypeName = 'savedGridType';
// last page name - used to control text entry on startup
String _lastPage = PageName.main.toString();
static const String _lastPageName = 'lastPageName';
// Flag to show if the grid type has change. Used to prevent 'show on start'
// triggering the text entry box after the grid layout has been changed by the user
bool _backFromOptionsPage = false;
final String _backFromOptionsPageName = 'fromOptions';
///
/// Hive management methods and global options setting
///
void setHiveBox(Box b) {
_box = b; // Pass the hive management object
print(
'Options hive box. Box is open: ${_box.isOpen} Box has values: ${_box.isNotEmpty}.');
// Set screen size factor for web vs tablet
if (kIsWeb) {
_tabletF = 0.4; // Factor components by 0.4 for web
} else {
_tabletF = 0.6; // Factor components by 0.6 for tablets
}
// Retrieve any option data values
if (_box.get(_lightModeName) != null) {
_lightMode = _box.get(_lightModeName);
} else {
print('Cannot find $_lightModeName');
_box.put(_lightModeName, _lightMode);
}
if (_box.get(_showGridName) != null) {
_showGrid = _box.get(_showGridName);
} else {
_box.put(_showGridName, _showGrid);
}
if (_box.get(_editOnStartName) != null) {
_editOnStart = _box.get(_editOnStartName);
} else {
_box.put(_editOnStartName, _editOnStart);
}
if (_box.get(_charTypeName) != null) {
String temp = _box.get(_charTypeName);
_charType = getCharEnum(temp);
} else {
_box.put(_charTypeName, _charType.toString());
}
if (_box.get(_themeModeName) != null) {
_themeMode = _box.get(_themeModeName);
} else {
_box.put(_themeModeName, _themeMode);
}
if (_box.get(_gridTypeName) != null) {
String temp = _box.get(_gridTypeName);
_gridType = getGridEnum(temp);
} else {
_box.put(_gridTypeName, _gridType.toString());
}
if (_box.get(_savedGridTypeName) != null) {
String temp = _box.get(_savedGridTypeName);
_savedGridType = getGridEnum(temp);
} else {
_box.put(_savedGridTypeName, _savedGridType.toString());
}
if (_box.get(_backFromOptionsPageName) != null) {
_box.put(_backFromOptionsPageName, _backFromOptionsPage);
} else {
_box.put(_backFromOptionsPageName, _backFromOptionsPage);
}
// Load last page value or reset if doesn't exit
if (_box.get(_lastPageName) != null) {
_box.put(_lastPageName, _lastPage);
} else {
_box.put(_lastPageName, _lastPage);
}
_box.flush(); // Make sure everything is written to the disk
}
}
Command line output:
flutter run -d chrome --web-renderer html --web-port 5555
Launching lib\main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome... 18.6s
This app is linked to the debug service: ws://127.0.0.1:54752/JAXqfQgauf4=/ws
Debug service listening on ws://127.0.0.1:54752/JAXqfQgauf4=/ws
Running with sound null safety
To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".
An Observatory debugger and profiler on Chrome is available at: http://127.0.0.1:54752/JAXqfQgauf4=
The Flutter DevTools debugger and profiler on Chrome is available at:
http://127.0.0.1:9101?uri=http://127.0.0.1:54752/JAXqfQgauf4=
build() Before first run. Extracting box keys. Attempt 1...
First run. Hive box is open: true Box has values: false
Options hive box. Box is open: true Box has values: false.
Cannot find lightMode
build() After first run. Extracting box keys. Attempt 2...
box keys: charType
box keys: editOnStart
box keys: fromOptions
box keys: gridType
box keys: lastPageName
box keys: lightMode
box keys: savedGridType
box keys: showGrid
box keys: themeMode
Doing init MyPageState
Options hive box. Box is open: true Box has values: true.
Application finished.
Update #1
Since originally posting I have tried deleting the box and re-running the app in case this was caused by a corrupt file. That hasn't made any difference.
I have also tried adding a .then to the openBox() command in case this is yet another async programming issue, but that hasn't made a difference either, i.e.
await Hive.openBox(_boxName).then((value) {
print('value is $value');
runApp(MyApp());
});
Update #2
So, it took me a while to work this out, but I create my box values on the first run if they don't already exist (to address the use case of the first ever run of the app). If I remove all the put statements in the setHiveBox() method then I get the problem consistently. In other words, there are no values in the box until my Options class creates them when the app runs. This suggests that the data is not being saved to the disk by app. I have compared both main.dart and options.dart with last known working versions and can't see any obvious differences. What could stop the application from saving the data to the disk? Just to note, I have tested another app I developed that uses Hive and this continues to work perfectly. That uses the same version of Hive as this app does.
I fixed this by doing a flutter clean on the project, deleting the flutter installation (deleting the install folder from the disk completely), downloading and re-installing flutter and then doing a flutter pub get on the project folder.
I had previously tried a flutter clean and flutter pub get on their own and this didn't fix the problem so maybe something had gone wrong in the flutter folder itself after the last upgrade? Anyway, a clean install of everything has solved the problem.

How to test browser url route for Flutter web?

I am working on testing how my navigator 2.0 setup handles url changes in the browser in flutter web.
The closest i have come to being able to test how my app handles url changes is to manually update state in the RouterDelegate by calling the setNewRoutePath with a config from the RouteInformationParser.
I would really like to test the navigator closer to the origin of the url change.
Any ideas and pointers would be appreciated.
My current code looks like this:
//Pass routeInformation to RouterInformationParser
RouteInformation selectShopRoute = RouteInformation(location: '/selectshop?token=321');
RouterConfig selectShopConfig = await app.myRouteParser.parseRouteInformation(selectShopRoute);
await app.myRouterDelegate.setNewRoutePath(selectShopConfig);
await tester.pumpAndSettle();
//Verify that navigator state is select shop
expect(app.myRouterDelegate.currentScreen, RouterEnum.selectshop);
//Verify that navigator token is set correctly
expect(app.myRouterDelegate.token, '321');
I had the same question and could not find a good approach. I came up with a way to test our code and wanted to share it to you.
Basically, we have a custom RouteInformationParser, in which a location is added only for the testing purpose.
class MyRouteInformationParser
extends RouteInformationParser<PageConfiguration> {
String? customPath; // only use for testing
#override
Future<PageConfiguration> parseRouteInformation(
RouteInformation routeInformation,
) async {
final location = customPath ?? routeInformation.location;
// Compute the configuration based on the location
return PageConfiguration()
}
}
In the widget test, we just create the route information parser and use it with the MaterialApp. Changing the customPath during testing has similar effect as changing the URL of the web browser.
final informationParser = MyRouteInformationParser();
informationParser.customPath = "my/expected/path";

Fully restart(discard and recreate) the whole flutter app programmatically [duplicate]

In production mode, is there a way to force a full restart of the application (I am not talking about a hot reload at development time!).
Practical use cases:
At initialization process the application detects that there is no network connection. The lack of network connectivity might have prevented a correct start up (e.g. loading of external resource such as JSON files...).
During the initial handshaking, new versions of some important resources need to be downloaded (kind of update).
In both use cases, I would like the application to proceed with a full restart, rather than having to build a complex logic at the ApplicationState level.
You could wrap your whole app into a statefulwidget. And when you want to restart you app, rebuild that statefulwidget with a child that possess a different Key.
This would make you loose the whole state of your app.
import 'package:flutter/material.dart';
void main() {
runApp(
RestartWidget(
child: MaterialApp(),
),
);
}
class RestartWidget extends StatefulWidget {
RestartWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>().restartApp();
}
#override
_RestartWidgetState createState() => _RestartWidgetState();
}
class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();
void restartApp() {
setState(() {
key = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
In this example you can reset your app from everywhere using RestartWidget.restartApp(context).
The flutter_phoenix package is based on Rémi Rousselet's answer, making it even simpler.
void main() {
runApp(
Phoenix(
child: App(),
),
);
}
Then when you need to restart the app, just call:
Phoenix.rebirth(context);
I developed the restart_app plugin to restart the whole app natively.
Update:
For anyone who get this exception:
MissingPluginException(No implementation found for method restartApp on channel restart)
Just stop and rebuild the app.
You can also use the runApp(new MyWidget) function to do something similar
This is what this function does:
Inflate the given widget and attach it to the screen.
The widget is given constraints during layout that force it to fill the entire screen. If you wish to align your widget to one side of the screen (e.g., the top), consider using the Align widget. If you wish to center your widget, you can also use the Center widget
Calling runApp again will detach the previous root widget from the screen and attach the given widget in its place. The new widget tree is compared against the previous widget tree and any differences are applied to the underlying render tree, similar to what happens when a StatefulWidget rebuilds after calling State.setState.
https://docs.flutter.io/flutter/widgets/runApp.html
So simple package: flutter_restart
dependencies:
flutter_restart: ^0.0.3
to use:
void _restartApp() async {
FlutterRestart.restartApp();
}
I just want to add Regarding I have Tried #Remi answer which works great on most of the cases to restart the app. The only problem with the answer is that some things if you are doing Navigation route extensively you probably go to a state which It gives you an error like,
The method 'restartApp' was called on null.
To resolve this error you have to know the Context and use Navigator.of(context).pop(); multiples times back. For me, the solution is that just go to the initial route. It will inject all the states from a new. Where you want to restart just add this Line.
Navigator.pushNamedAndRemoveUntil(context,'/',(_) => false);
If you want to only restart a specific widget then the Remi solution is awesome. Thanks for the solution Remi though. It help me understand states in flutter.
I have found Hossein's restart_app package also pretty useful for native restarts (not only on Flutter level).
To everyone having the MissingPluginException error, just reinstall the app again on the device, means that hot reload won't work. The app has native methods which need to compiled in the Android/iOS App.
I wanted to restart my app after logout.
so I used https://pub.dev/packages/flutter_phoenix (flutter phoenix).
It worked for me.
Install flutter_phoenix by running this command on your terminal inside your flutter app directory.
$ flutter pub add flutter_phoenix
Import it inside your "main.dart".
Wrap your root widget inside Phoenix.
runApp(
Phoenix(
child: MyApp()
));
Now you can call this wherever you want to restart your app :-
Phoenix.rebirth(context)
Note: flutter_phoenix does not restart the app on OS level, it only restarts the app on app level.
Thecnically this is not a restart but it will work for most of the scenarios:
// Remove any route in the stack
Navigator.of(context).popUntil((route) => false);
// Add the first route. Note MyApp() would be your first widget to the app.
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => const MyApp()),
);
Follow the steps-
Go to your terminal and type in the following:
flutter pub add flutter_restart
This will update some dependencies in pubspec.yaml file.
Import the following package in whichever file you want to implement the restart code-
import 'package:flutter_restart/flutter_restart.dart';
Create a void function
void _restartApp() async {
await FlutterRestart.restartApp();
}
Write this wherever you want to start the app-
_restartApp();
I tried the above suggested methods and none of them worked and i was using getx.
so i ended up modified the accepted answer with a delay as a workaround and it works now.
class RestartAppWidget extends StatefulWidget {
RestartAppWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartAppWidgetState>().restartApp();
}
#override
_RestartAppWidgetState createState() => _RestartAppWidgetState();
}
class _RestartAppWidgetState extends State<RestartAppWidget> {
bool restarting = false;
void restartApp() async {
restarting = true; // restart variable is set to true
setState(() {});
Future.delayed(Duration(milliseconds: 300)).then((value) {
setState(() {
restarting = false; //restart variable is set to false
});
});
// setState(() {
// key = UniqueKey();
// });
}
#override
Widget build(BuildContext context) {
if (restarting) {
return SizedBox(); //an empty Sizedbox is displayed for 300 milliseconds you can add a loader if you want
}
return SizedBox(
child: widget.child,
);
}
}`
wrap the root widget with RestartAppWidget
runApp(RestartAppWidget(
child: MyApp(),
))
you can use this code to restart the app at flutter level
RestartAppWidget.restartApp(Get.context);

How to force a Flutter application restart (in production mode)?

In production mode, is there a way to force a full restart of the application (I am not talking about a hot reload at development time!).
Practical use cases:
At initialization process the application detects that there is no network connection. The lack of network connectivity might have prevented a correct start up (e.g. loading of external resource such as JSON files...).
During the initial handshaking, new versions of some important resources need to be downloaded (kind of update).
In both use cases, I would like the application to proceed with a full restart, rather than having to build a complex logic at the ApplicationState level.
You could wrap your whole app into a statefulwidget. And when you want to restart you app, rebuild that statefulwidget with a child that possess a different Key.
This would make you loose the whole state of your app.
import 'package:flutter/material.dart';
void main() {
runApp(
RestartWidget(
child: MaterialApp(),
),
);
}
class RestartWidget extends StatefulWidget {
RestartWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>().restartApp();
}
#override
_RestartWidgetState createState() => _RestartWidgetState();
}
class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();
void restartApp() {
setState(() {
key = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
In this example you can reset your app from everywhere using RestartWidget.restartApp(context).
The flutter_phoenix package is based on Rémi Rousselet's answer, making it even simpler.
void main() {
runApp(
Phoenix(
child: App(),
),
);
}
Then when you need to restart the app, just call:
Phoenix.rebirth(context);
I developed the restart_app plugin to restart the whole app natively.
Update:
For anyone who get this exception:
MissingPluginException(No implementation found for method restartApp on channel restart)
Just stop and rebuild the app.
You can also use the runApp(new MyWidget) function to do something similar
This is what this function does:
Inflate the given widget and attach it to the screen.
The widget is given constraints during layout that force it to fill the entire screen. If you wish to align your widget to one side of the screen (e.g., the top), consider using the Align widget. If you wish to center your widget, you can also use the Center widget
Calling runApp again will detach the previous root widget from the screen and attach the given widget in its place. The new widget tree is compared against the previous widget tree and any differences are applied to the underlying render tree, similar to what happens when a StatefulWidget rebuilds after calling State.setState.
https://docs.flutter.io/flutter/widgets/runApp.html
So simple package: flutter_restart
dependencies:
flutter_restart: ^0.0.3
to use:
void _restartApp() async {
FlutterRestart.restartApp();
}
I just want to add Regarding I have Tried #Remi answer which works great on most of the cases to restart the app. The only problem with the answer is that some things if you are doing Navigation route extensively you probably go to a state which It gives you an error like,
The method 'restartApp' was called on null.
To resolve this error you have to know the Context and use Navigator.of(context).pop(); multiples times back. For me, the solution is that just go to the initial route. It will inject all the states from a new. Where you want to restart just add this Line.
Navigator.pushNamedAndRemoveUntil(context,'/',(_) => false);
If you want to only restart a specific widget then the Remi solution is awesome. Thanks for the solution Remi though. It help me understand states in flutter.
I have found Hossein's restart_app package also pretty useful for native restarts (not only on Flutter level).
To everyone having the MissingPluginException error, just reinstall the app again on the device, means that hot reload won't work. The app has native methods which need to compiled in the Android/iOS App.
I wanted to restart my app after logout.
so I used https://pub.dev/packages/flutter_phoenix (flutter phoenix).
It worked for me.
Install flutter_phoenix by running this command on your terminal inside your flutter app directory.
$ flutter pub add flutter_phoenix
Import it inside your "main.dart".
Wrap your root widget inside Phoenix.
runApp(
Phoenix(
child: MyApp()
));
Now you can call this wherever you want to restart your app :-
Phoenix.rebirth(context)
Note: flutter_phoenix does not restart the app on OS level, it only restarts the app on app level.
Thecnically this is not a restart but it will work for most of the scenarios:
// Remove any route in the stack
Navigator.of(context).popUntil((route) => false);
// Add the first route. Note MyApp() would be your first widget to the app.
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => const MyApp()),
);
Follow the steps-
Go to your terminal and type in the following:
flutter pub add flutter_restart
This will update some dependencies in pubspec.yaml file.
Import the following package in whichever file you want to implement the restart code-
import 'package:flutter_restart/flutter_restart.dart';
Create a void function
void _restartApp() async {
await FlutterRestart.restartApp();
}
Write this wherever you want to start the app-
_restartApp();
I tried the above suggested methods and none of them worked and i was using getx.
so i ended up modified the accepted answer with a delay as a workaround and it works now.
class RestartAppWidget extends StatefulWidget {
RestartAppWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartAppWidgetState>().restartApp();
}
#override
_RestartAppWidgetState createState() => _RestartAppWidgetState();
}
class _RestartAppWidgetState extends State<RestartAppWidget> {
bool restarting = false;
void restartApp() async {
restarting = true; // restart variable is set to true
setState(() {});
Future.delayed(Duration(milliseconds: 300)).then((value) {
setState(() {
restarting = false; //restart variable is set to false
});
});
// setState(() {
// key = UniqueKey();
// });
}
#override
Widget build(BuildContext context) {
if (restarting) {
return SizedBox(); //an empty Sizedbox is displayed for 300 milliseconds you can add a loader if you want
}
return SizedBox(
child: widget.child,
);
}
}`
wrap the root widget with RestartAppWidget
runApp(RestartAppWidget(
child: MyApp(),
))
you can use this code to restart the app at flutter level
RestartAppWidget.restartApp(Get.context);