I am trying to show alert dialog for force update my app on my app version change using firebase remote config, calling versionCheck(context) from initState() everything is fine but getting error when I am calling showVersionDialog() method, here is my code
void main() => runApp(UniApp());
class UniApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _UniappMainState();
}
class _UniappMainState extends State<UniApp> {
AppTranslationsDelegate _newLocaleDelegate;
#override
void initState() {
super.initState();
setlocaleFromSharedPreference();
_newLocaleDelegate = AppTranslationsDelegate(newLocale: null);
UAAppContext.getInstance().onLocaleChanged = onLocaleChange;
//calling versionCheck
versionCheck(context);
}
versionCheck(context) async {
//Get Current installed version of app
final PackageInfo info = await PackageInfo.fromPlatform();
double currentVersion = double.parse(info.version.trim().replaceAll(".", ""));
//Get Latest version info from firebase config
final RemoteConfig remoteConfig = await RemoteConfig.instance;
try {
// Using default duration to force fetching from remote server.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
remoteConfig.setConfigSettings(RemoteConfigSettings(debugMode: true));
await remoteConfig.activateFetched();
remoteConfig.getString('force_update_current_version');
double newVersion = double.parse(remoteConfig
.getString('force_update_current_version')
.trim()
.replaceAll(".", ""));
print("cv-"+currentVersion.toString()+"nv--"+newVersion.toString());
if (newVersion > currentVersion) {
_showVersionDialog(context);
}
} on FetchThrottledException catch (exception) {
// Fetch throttled.
print(exception);
} catch (exception) {
print('Unable to fetch remote config. Cached or default values will be '
'used');
}
}
//Show Dialog to force user to update
_showVersionDialog(context) async {
await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
String title = "New Update Available";
String message =
"There is a newer version of app available please update it now.";
String btnLabel = "Update Now";
String btnLabelCancel = "Later";
return new AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(btnLabel),
onPressed: () => _launchURL(CommonConstants.PLAY_STORE_URL),
),
FlatButton(
child: Text(btnLabelCancel),
onPressed: () => Navigator.pop(context),
),
],
);
},
);
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void onLocaleChange(Locale locale) {
setState(() {
UAAppContext.getInstance().changeLanguage(locale.languageCode);
_newLocaleDelegate = AppTranslationsDelegate(newLocale: locale);
});
}
setlocaleFromSharedPreference() {
UAAppContext.getInstance().getLocale().then((locale) {
if (locale == 'en') return;
setState(() {
_newLocaleDelegate = AppTranslationsDelegate(newLocale: Locale(locale));
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => SplashScreen(),
CommonConstants.homeRoute: (context) { RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null && r.appId != null && r.appId.isNotEmpty) {
return HomeScreen(
parentAppId: r.appId
);
} else return HomeScreen();},
CommonConstants.loginRoute: (context) => LoginScreen(),
CommonConstants.projectGroupRoute: (context) {
RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null && r.appId != null && r.appId.isNotEmpty) {
return ProjectGroupScreen(
appId: r.appId,
attributes: r.groupingAttributes,
sortType: r.sortType,
);
} else
return SplashScreen();
},
CommonConstants.projectListRoute: (context) {
RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null && r.appId != null && r.appId.isNotEmpty) {
return ProjectListScreen(
appId: r.appId,
sortType: r.sortType,
groupingKey: r.groupingKey,
groupingValue: r.groupingValue,
projectMasterDataTableList: r.projectMasterDataTableList,
);
} else
return SplashScreen();
},
CommonConstants.projectFormRoute: (context) {
RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null && r.appId != null && r.appId.isNotEmpty) {
return ProjectFormScreen(
appId: r.appId,
projectId: r.projectId,
formActiontype: r.formActionType,
projectMasterDataKeyToValueMap: r.projectFieldsKeyToValue,
);
} else
return SplashScreen();
},
CommonConstants.getOTPRoute: (context) => GetOTPScreen(),
CommonConstants.changePasswordRoute: (context) =>
ChangePasswordScreen(),
CommonConstants.userRegistrationRoute: (context) =>
UserRegisterScreen(),
CommonConstants.downloadsRoute: (context) => DownloadScreen(),
CommonConstants.filterRoute: (context) {
RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null && r.appId != null && r.appId.isNotEmpty) {
return FilterScreen(
appId: r.appId,
projectList: UAAppContext.getInstance().projectList,
filterKeyToValue:
UAAppContext.getInstance().filterSelectedValueMap,
);
} else
return SplashScreen();
},
CommonConstants.geoTaggingRoute: (context) {
RouteParameters r = ModalRoute.of(context).settings.arguments;
if (r != null &&
r.geoTaggingWidgetId != null &&
r.geoTaggingWidgetId.isNotEmpty) {
return GeotaggingWidget(
ctxt: r.context,
id: r.geoTaggingWidgetId,
gpsValidation: r.gpsValidation,
projLat: r.projLat,
projLon: r.projLon,
);
} else
return SplashScreen();
},
CommonConstants.profileRoute: (context) => UserProfileScreen(),
},
debugShowCheckedModeBanner: false,
// theme: UniappColorTheme.defaultTheme,
theme: UniappColorTheme.getTheme(),
localizationsDelegates: [
_newLocaleDelegate,
//provides localised strings
GlobalMaterialLocalizations.delegate,
//provides RTL support
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale("en", ""),
const Locale("hi", ""),
const Locale("or", "")
],
);
}
}
These are error msg I am getting when showing showVersionDialog() method, not getting what actually means it.
E/flutter (12951): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: No MaterialLocalizations found.
E/flutter (12951): UniApp widgets require MaterialLocalizations to be provided by a Localizations widget ancestor.
E/flutter (12951): Localizations are used to generate many different messages, labels, and abbreviations which are used by the material library.
E/flutter (12951): To introduce a MaterialLocalizations, either use a MaterialApp at the root of your application to include them automatically, or add a Localization widget with a MaterialLocalizations delegate.
E/flutter (12951): The specific widget that could not find a MaterialLocalizations ancestor was:
E/flutter (12951): UniApp
E/flutter (12951): The ancestors of this widget were:
E/flutter (12951): [root]
E/flutter (12951): #0 debugCheckHasMaterialLocalizations.<anonymous closure> (package:flutter/src/material/debug.dart:72:7)
E/flutter (12951): #1 debugCheckHasMaterialLocalizations (package:flutter/src/material/debug.dart:92:4)
E/flutter (12951): #2 showDialog (package:flutter/src/material/dialog.dart:843:10)
E/flutter (12951): #3 _UniappMainState._showVersionDialog (package:Uniapp/main.dart:80:11)
E/flutter (12951): #4 _UniappMainState.versionCheck (package:Uniapp/main.dart:67:9)
E/flutter (12951): <asynchronous suspension>
E/flutter (12951): #5 _UniappMainState.initState (package:Uniapp/main.dart:44:5)
I just solve this issue by creating a singleton class (MySingletonClass) with a variable
BuildContext get context => _context;
got this variable
MySingletonClass.getInstance().context;
Passing singleton class context to showdialog contex
final context = MySingletonClass.getInstance().context;
//calling showVersionDialong
_showVersionDialog(context);
I had a similar problem when using rflutter_alert package for showing pop-up alert dialog.
My code structure was :
void main () {
runApp(QuizPage)
}
inside QuizPage , the build method returns:
return MaterialApp(
home: Scaffold())
I was able to solve it by following the second approche in the instructions in this article: https://www.fluttercampus.com/guide/70/how-to-solve-no-materiallocalizations-found-error-exception-in-flutter/
Now my code structure is:
runApp(MaterialApp(home: MyApp()))
MyApp returns QuizPage
Related
I am trying to make a Flutter app that contains a login screen and then home screen (only two screens for now). I am using Flutter Secure Storage and Http libraries too.
Whenever the app launches, I want the app to check if two props, accessKey and accessId, are stored in the Secure storage. If accessId is not found, it is auto-generated and assigned with the Uuid library. Whereas the accessKey is not generated locally and is provided by an API.
App navigates to:
1). HomeScreen, if accessKey is stored in Secure Storage and authentication succeeds.
2). SignInScreen, if accessKey is not found or authentication fails.
My problem is, Secure Storage keeps throwing error "Null check operator used on a null value", everytime I perform a read operation. I have initialized the storage variable, yet this problem keeps happening.
Here is my Secure Storage class code:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class FAS {
static FlutterSecureStorage? _storage;
static void init() {
_storage = const FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
}
static Future<String?> read(String key) async {
return _storage!.read(key: key);
}
static Future<Map<String, String>> readAll() async {
return _storage!.readAll();
}
static Future<void> write(String key, String value) async {
await _storage!.write(key: key, value: value);
}
static Future<void> delete(String key) async {
await _storage!.delete(key: key);
}
static Future<void> deleteAll() async {
await _storage!.deleteAll();
}
}
Before, the section of that code was this:
static const FlutterSecureStorage _storage = FlutterSecureStorage(aOptions: AndroidOptions(encryptedSharedPreferences: true));
There was no init method.
Yet I keep getting the same error.
Here is my main.dart:
import 'package:flutter/material.dart';
import 'package:unified_bot_app/pages/home_page.dart';
import 'package:uuid/uuid.dart';
import './models/fas.dart';
import './pages/sign_in_page.dart';
import './request_methods.dart';
Future<void> tryAssignAccessId() async {
String? accessId = await FAS.read("ACCESS_ID");
if (accessId == null) {
await FAS.write("ACCESS_ID", (const Uuid()).v4());
}
}
void main() {
FAS.init();
tryAssignAccessId(); // <- Error
runApp(
MaterialApp(
home: FutureBuilder<bool>(
builder: (ctx, a) {
if (a.connectionState == ConnectionState.done) {
if (a.data!) return HomePage();
return const SignInPage();
}
return const Center(child: CircularProgressIndicator());
},
future: () async {
try {
String? accessKey = await FAS.read("ACCESS_KEY");
if (accessKey == null) {
return false;
}
return await HTTP.authenticate(accessKey);
} catch (e) {
return false;
}
}(),
),
theme: ThemeData(fontFamily: "Josefin Sans"),
),
);
}
And here is the output I get when I restart the app:
Restarted application in 531ms. E/flutter (20760):
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Null
check operator used on a null value E/flutter (20760): #0
MethodChannel.binaryMessenger
package:flutter/…/services/platform_channel.dart:121 E/flutter
(20760): #1 MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:146 E/flutter
(20760): #2 MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:329 E/flutter
(20760): #3 MethodChannelFlutterSecureStorage.read
package:flutter_secure_storage_platform_interface/src/method_channel_flutter_secure_storage.dart:49
E/flutter (20760): #4 FlutterSecureStorage.read
package:flutter_secure_storage/flutter_secure_storage.dart:91
E/flutter (20760): #5 FAS.read
package:unified_bot_app/models/fas.dart:13 E/flutter (20760): #6
tryAssignAccessId package:unified_bot_app/main.dart:10 E/flutter
(20760): #7 main package:unified_bot_app/main.dart:18 E/flutter
(20760): #8 _runMainZoned..
(dart:ui/hooks.dart:145:25) E/flutter (20760): #9 _rootRun
(dart:async/zone.dart:1428:13) E/flutter (20760): #10
_CustomZone.run (dart:async/zone.dart:1328:19) E/flutter (20760): #11 _runZoned (dart:async/zone.dart:1863:10) E/flutter (20760): #12 runZonedGuarded (dart:async/zone.dart:1851:12) E/flutter (20760): #13
_runMainZoned. (dart:ui/hooks.dart:141:5) E/flutter (20760): #14 _delayEntrypointInvocation.
(dart:isolate-patch/isolate_patch.dart:283:19) E/flutter (20760): #15
_RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12) E/flutter (20760):
D/EGL_emulation(20760): app_time_stats: avg=14143.50ms min=14143.50ms
max=14143.50ms count=1
However, the error disappears when I place first two lines in the end (after runApp(..)):
void main() {
runApp(
MaterialApp(
home: FutureBuilder<bool>(
builder: (ctx, a) {
if (a.connectionState == ConnectionState.done) {
if (a.data!) return HomePage();
return const SignInPage();
}
return const Center(child: CircularProgressIndicator());
},
future: () async {
try {
String? accessKey = await FAS.read("ACCESS_KEY"); // <- Error re-appears here
if (accessKey == null) {
return false;
}
return await HTTP.authenticate(accessKey);
} catch (e) {
return false;
}
}(),
),
theme: ThemeData(fontFamily: "Josefin Sans"),
),
);
FAS.init();
tryAssignAccessId();
}
But doing that, the error then re-appears on the marked line.
I am confused. What's happening?
Any help is appreciated.
Edit 1:
I tried calling the init() method before I call the second read() method, yet the same error is thrown.
Updated section:
future: () async {
try {
FAS.init();
String? accessKey = await FAS.read("ACCESS_KEY");
if (accessKey == null) {
return false;
}
return await HTTP.authenticate(accessKey);
} catch (e) {
print(e);
return false;
}
}(),
Console output:
Restarted application in 510ms. I/flutter (20760): Null check operator
used on a null value D/EGL_emulation(20760): app_time_stats:
avg=1899.03ms min=1899.03ms max=1899.03ms count=1
I solved this by adding
WidgetsFlutterBinding.ensureInitialized();
to the main() method before runApp().
I fixed this issue by adding a new Blank page containing all the methods.
Blank page file:
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../models/fas.dart';
import '../request_methods.dart';
import './home_page.dart';
import './sign_in_page.dart';
class BlankPage extends StatelessWidget {
Future<void> _tryAssignAccessId() async {
String? accessId = await FAS.read("ACCESS_ID");
if (accessId == null) {
await FAS.write("ACCESS_ID", (const Uuid()).v4());
}
}
Future<bool> _checkAuth() async {
try {
String? accessKey = await FAS.read("ACCESS_KEY");
if (accessKey == null) {
return false;
}
return await HTTP.authenticate(accessKey);
} catch (e) {
return false;
}
}
#override
Widget build(BuildContext context) {
FAS.init();
_tryAssignAccessId();
_checkAuth().then((result) {
if (result) {
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (ctx) => HomePage()));
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (ctx) => const SignInPage()));
}
});
return Scaffold();
}
}
Updated main.dart (for ref.):
import 'package:flutter/material.dart';
import './pages/blank.dart';
void main() {
runApp(
MaterialApp(
home: BlankPage(),
theme: ThemeData(fontFamily: "Josefin Sans"),
),
);
}
Context
I have this AppUser class:
#immutable
class AppUser {
const AppUser({
this.displayName,
this.email,
required this.emailVerified,
this.phoneNumber,
this.photoURL,
required this.uid,
});
AppUser.fromFirebaseUser(User user)
: displayName = user.displayName,
email = user.email,
emailVerified = user.emailVerified,
phoneNumber = user.phoneNumber,
photoURL = user.photoURL,
uid = user.uid;
final String? displayName;
final String? email;
final bool emailVerified;
final String? phoneNumber;
final String? photoURL;
final String uid;
}
In order to manage and use the current user signed in, I have this AppUserController class:
class AppUserController extends StateNotifier<AppUser> {
AppUserController()
: super(
const AppUser(
emailVerified: false,
uid: '',
),
);
Stream<User?> get onAuthStateChanges =>
FirebaseAuth.instance.authStateChanges();
set setAppUser(AppUser appUser) {
state = appUser;
}
Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
}
}
Then, I created 2 providers:
final appUserProvider =
StateNotifierProvider<AppUserController, AppUser>((ref) {
return AppUserController();
});
final appUserStreamProvider = StreamProvider<AppUser?>((ref) {
return ref
.read(appUserProvider.notifier)
.onAuthStateChanges
.map<AppUser?>((user) {
return user != null ? AppUser.fromFirebaseUser(user) : null;
});
});
I need to manage a user’s budgets list. Also, I have to synchronize this list with a Cloud Firestore database, so I created the BudgetsService class:
class BudgetsService {
BudgetsService({
required this.uid,
}) : budgetsRef = FirebaseFirestore.instance
.collection(FirestorePath.budgetsCollection(uid))
.withConverter<Budget>(
fromFirestore: (snapshot, _) => Budget.fromMap(snapshot.data()!),
toFirestore: (budget, _) => budget.toMap(),
);
String uid;
final CollectionReference<Budget> budgetsRef;
Future<void> addUpdate(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).set(budget);
}
Future<void> delete(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).delete();
}
String documentPath(Budget budget) => FirestorePath.budgetDoc(uid, budget);
Future<List<Budget>> getBudgets() async {
final list = await budgetsRef.get();
return list.docs.map((e) => e.data()).toList();
}
}
I use this class through budgetsServiceProvider provider:
final budgetsServiceProvider = Provider<BudgetsService>((ref) {
final AppUser appUser = ref.watch(appUserProvider);
final String uid = appUser.uid;
return BudgetsService(uid: uid);
});
I use BudgetsService class only to interact with the online database. For the rest, I manage the user’s budget list with BudgetsController class:
class BudgetsController extends StateNotifier<List<Budget>> {
BudgetsController() : super(<Budget>[]);
List<String> get names => state.map((b) => b.name).toList();
Future<void> addUpdate(Budget budget, BudgetsService budgetsService) async {
await budgetsService.addUpdate(budget);
if (budgetAlreadyExists(budget)) {
final int index = indexOf(budget);
final List<Budget> newState = [...state];
newState[index] = budget;
state = newState..sort();
} else {
state = [...state, budget]..sort();
}
}
bool budgetAlreadyExists(Budget budget) => names.contains(budget.name);
Future<void> delete(Budget budget, BudgetsService budgetsService) async {
await budgetsService.delete(budget);
final int index = indexOf(budget);
if (index != -1) {
final List<Budget> newState = [...state]
..removeAt(index)
..sort();
state = newState;
}
}
Future<void> retrieveBudgets(BudgetsService budgetsService) async {
state = await budgetsService.getBudgets();
}
int indexOf(Budget budget) => state.indexWhere((b) => b.name == budget.name);
}
I use this class through budgetsProvider provider:
final budgetsProvider =
StateNotifierProvider<BudgetsController, List<Budget>>((ref) {
return BudgetsController();
});
After the user is signed in, my SwitchScreen widget navigates to ConsoleScreen:
class SwitchScreen extends HookWidget {
const SwitchScreen({
Key? key,
}) : super(key: key);
static const route = '/switch';
#override
Widget build(BuildContext context) {
final appUserStream =
useProvider<AsyncValue<AppUser?>>(appUserStreamProvider);
final googleSignIn =
useProvider<GoogleSignInService>(googleSignInServiceProvider);
final appUserController =
useProvider<AppUserController>(appUserProvider.notifier);
return appUserStream.when(
data: (data) {
if (data != null) {
appUserController.setAppUser = data;
final budgetsService = useProvider(budgetsServiceProvider);
return const ConsoleScreen();
} else {
return SignInScreen(
onGooglePressed: googleSignIn.signInWithGoogle,
);
}
},
loading: () {
return const Scaffold(
body: Center(
child: LinearProgressIndicator(),
),
);
},
error: (error, stack) {
return Scaffold(
body: Center(
child: Text('Error: $error'),
),
);
},
);
}
}
Problem
The first time I build the app, I have no problem. But when I perform the hot reload, I get the following error message:
══════ Exception caught by widgets library ═══════════════════════════════════
The following Error was thrown building SwitchScreen(dirty, dependencies: [UncontrolledProviderScope], AsyncValue<AppUser?>.data(value: Instance of 'AppUser'), Instance of 'GoogleSignInService', Instance of 'AppUserController'):
Instance of 'Error'
The relevant error-causing widget was
SwitchScreen
lib\main.dart:67
When the exception was thrown, this was the stack
#0 StateNotifier.state=
package:state_notifier/state_notifier.dart:173
#1 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
#2 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
#3 _$AsyncData.when
package:riverpod/src/common.freezed.dart:148
#4 SwitchScreen.build
package:financesmanager/screens/switch_screen.dart:28
...
════════════════════════════════════════════════════════════════════════════════
E/flutter (13932): [ERROR:flutter/shell/common/shell.cc(103)] Dart Unhandled Exception: setState() or markNeedsBuild() called during build.
E/flutter (13932): This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
E/flutter (13932): The widget on which setState() or markNeedsBuild() was called was:
E/flutter (13932): UncontrolledProviderScope
E/flutter (13932): The widget which was currently being built when the offending call was made was:
E/flutter (13932): SwitchScreen, stack trace: #0 Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4217
E/flutter (13932): #1 Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4232
E/flutter (13932): #2 ProviderElement._debugMarkWillChange.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:660
E/flutter (13932): #3 ProviderElement._debugMarkWillChange
package:riverpod/…/framework/base_provider.dart:664
E/flutter (13932): #4 ProviderStateBase.exposedValue=.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:900
E/flutter (13932): #5 ProviderStateBase.exposedValue=
package:riverpod/…/framework/base_provider.dart:902
E/flutter (13932): #6 _StateNotifierProviderState._listener
package:riverpod/src/state_notifier_provider.dart:92
E/flutter (13932): #7 StateNotifier.state=
package:state_notifier/state_notifier.dart:162
E/flutter (13932): #8 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
E/flutter (13932): #9 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
Question
How can I solve the problem?
Thank you very much!
Update (2021-06-08)
In my main.dart file I have:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(ProviderScope(child: FMApp()));
}
class FMApp extends HookWidget {
FMApp({
Key? key,
}) : super(key: key);
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
final darkTheme = AppTheme.theme(Brightness.dark);
final lightTheme = AppTheme.theme(Brightness.light);
final isLightTheme = useProvider<bool>(themePreferenceProvider);
final theme = isLightTheme ? lightTheme : darkTheme;
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.hasError) {
return FlutterFireInitErrorScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
error: snapshot.error,
theme: theme,
);
}
if (snapshot.connectionState == ConnectionState.done) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FM App',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'es'),
Locale.fromSubtags(languageCode: 'it'),
],
darkTheme: darkTheme,
theme: theme,
initialRoute: SwitchScreen.route,
routes: {
SwitchScreen.route: (context) => const SwitchScreen(),
},
);
}
return FlutterFireInitWaitingScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
theme: theme,
);
},
);
}
}
Possible solution
For now I solved it by replacing, in switch_screen.dart file, this code:
final budgetsService = useProvider(budgetsServiceProvider);
final budgetsController = context.read<BudgetsController>(budgetsProvider.notifier);
budgetsController.retrieveBudgets(budgetsService);
with the following:
final budgetsService = BudgetsService(uid: data.uid);
context
.read(budgetsControllerProvider)
.retrieveBudgets(budgetsService);
What do you think? Is this a good solution? Is there a better one? Thank you!
The interpretation of the error is that two widgets are updating at the same time, probably because they watch the same provider.
When a Child Widget tries to rebuild while its Parent Widget also tries to rebuild, it generates this error. To solve this error, only the Parent Widget needs to rebuild, because the Child Widget will automatically rebuild.
Unfortunately, in the code you provide, I cannot see from where your SwitchScreen is displayed so I cannot tell you where the exact problem could be.
I use Firebase dynamic links and also named routes. What I want is to install a global listener for the dynamic link events and forward to register page if a token is provided. In the code below I got the exception The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget. which means I have to put navigation code below the home: property of MaterialApp. But when doing this I had to implement the dynamic links event handler for earch route.
class MyApp extends StatelessWidget {
String title = "Framr";
#override
Widget build(BuildContext context) {
FirebaseDynamicLinks.instance.onLink(
onSuccess: (linkData) {
if (linkData != null) {
try {
Navigator.pushNamed(context, '/register', arguments: linkData);
// throws: The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
} catch(e) {
print(e);
}
}
return null;
}
);
return MaterialApp(
title: "...",
home: LoginPage(),
routes: {
'/createEvent': (context) => CreateEventPage(),
'/showEvent': (context) => ShowEventPage(),
'/register': (context) => RegisterPage(),
},
);
}
}
I was able to get this work by following the example provided from the dynamic link README with the use of the no_context_navigation package or GlobalKey to workaround around the lack of context to call Navigator.pushNamed(...). Note: You don't have to use no_context_navigation. You can implement the no context routing yourself. Here's an example.
// Add this
import 'package:no_context_navigation/no_context_navigation.dart';
void main() {
runApp(MaterialApp(
title: 'Dynamic Links Example',
// Add this
navigatorKey: NavigationService.navigationKey,
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => MyHomeWidget(), // Default home route
'/helloworld': (BuildContext context) => MyHelloWorldWidget(),
},
));
}
class MyHomeWidgetState extends State<MyHomeWidget> {
.
.
.
#override
void initState() {
super.initState();
this.initDynamicLinks();
}
void initDynamicLinks() async {
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
// Add this.
final NavigationService navService = NavigationService();
final Uri deepLink = dynamicLink?.link;
if (deepLink != null) {
// This doesn't work due to lack of context
// Navigator.pushNamed(context, deepLink.path);
// Use this instead
navService.pushNamed('/helloworld', args: dynamicLink);
}
},
onError: (OnLinkErrorException e) async {
print('onLinkError');
print(e.message);
}
);
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
if (deepLink != null) {
// This doesn't work due to lack of context
// Navigator.pushNamed(context, deepLink.path);
// Use this instead
navService.pushNamed('/helloworld', args: dynamicLink);
}
}
.
.
.
}
// pubspec.yaml
no_context_navigation: ^1.0.4
So, I have a project in flutter and I'm trying to build a list of Cards where the contents depends on my OrderModel class and I'm trying to use Provider to achieve this, but I get this error:
════════ Exception caught by scheduler library ══════════════════════════
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing listen: false.
To fix, write:
Provider.of(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
'package:provider/src/provider.dart':
Failed assertion: line 193 pos 7: 'context.owner.debugBuilding || listen == false || _debugIsInInheritedProviderUpdate'
When the exception was thrown, this was the stack
#2 Provider.of
package:provider/src/provider.dart:193
#3 _OrderHistoryState._onAfterBuild
package:shinier_store/screens/order_history.dart:60
#4 _OrderHistoryState.build.
package:shinier_store/screens/order_history.dart:67
#5 SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1102
#6 SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1049
...
═════════════════════════════════════════════════════════
Stacktrace for the Provider call at _onAfterBuild()
I/flutter ( 3092): #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:42:39)
I/flutter ( 3092): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:38:5)
I/flutter ( 3092): #2 Provider.of package:provider/src/provider.dart:193
I/flutter ( 3092): #3 _OrderHistoryState._onAfterBuild package:shinier_store/screens/order_history.dart:61
I/flutter ( 3092): #4 _OrderHistoryState.build.<anonymous closure>
package:shinier_store/screens/order_history.dart:71
I/flutter ( 3092): #5 SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1102
I/flutter ( 3092): #6 SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1049
I/flutter ( 3092): #7 SchedulerBinding._handleDrawFrame
package:flutter/…/scheduler/binding.dart:957
I/flutter ( 3092): #8 _rootRun (dart:async/zone.dart:1126:13)
I/flutter ( 3092): #9 _CustomZone.run (dart:async/zone.dart:1023:19)
I/flutter ( 3092): #10 _CustomZone.runGuarded (dart:async/zone.dart:925:7)
I/flutter ( 3092): #11 _invoke (dart:ui/hooks.dart:259:10)
I/flutter ( 3092): #12 _drawFrame (dart:ui/hooks.dart:217:3)
I don't know how can I possibly solve it since I added the listen:false to my Provider call. I tried using WidgetBinding, cause I thought the Provider call should be made after build is done but that didn't seem to solve the problem.
Here are the codes:
OrderModel class
class OrderModel extends ChangeNotifier {
List<Order> myOrders;
bool isLoading = true;
String errMsg;
int page = 1;
bool endPage = false;
void getMyOrder({UserModel userModel}) async {
try {
isLoading = true;
notifyListeners();
myOrders = await WooCommerce().getMyOrders(userModel: userModel, page: 1);
page = 1;
errMsg = null;
isLoading = false;
endPage = false;
notifyListeners();
} catch (err) {
errMsg =
"There is an issue with the app during request the data, please contact admin for fixing the issues " +
err.toString();
isLoading = false;
notifyListeners();
}
}
void loadMore({UserModel userModel}) async {
try {
isLoading = true;
page = page + 1;
notifyListeners();
var orders =
await WooCommerce().getMyOrders(userModel: userModel, page: page);
myOrders = [...myOrders, ...orders];
if (orders.length == 0) endPage = true;
errMsg = null;
isLoading = false;
notifyListeners();
} catch (err) {
errMsg =
"There is an issue with the app during request the data, please contact admin for fixing the issues " +
err.toString();
isLoading = false;
notifyListeners();
}
}
}
order_history.dart - state class
class _OrderHistoryState extends State<OrderHistory> {
void _onAfterBuild(BuildContext context){
Provider.of<OrderModel>(context, listen: false)
.getMyOrder(userModel: Provider.of<UserModel>(context));
}
#override
Widget build(BuildContext context) {
var formatter = DateFormat('dd-MM-yyyy');
var model = Provider.of<OrderModel>(context);
WidgetsBinding.instance.addPostFrameCallback((_) => _onAfterBuild(context));
return Scaffold(
appBar: AppBar(
title: Text(
'Order History',
style: TextStyle(fontWeight: FontWeight.bold),
),
elevation: 0.0),
body: model.myOrders == null ? Center() :
Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.separated(
separatorBuilder: (_, __) => SizedBox(height: 10.0),
itemCount: model.myOrders.length,
itemBuilder: (context, index) {
String stat = model.myOrders[index].status;
return Card(
color: _buildColor(stat),
elevation: 3.5,
...
}
}
main.dart - build method
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserModel()),
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => SearchModel()),
ChangeNotifierProvider(create: (_) => OrderModel()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
backgroundColor: Colors.white,
canvasColor: Colors.white,
),
home: MainTabs(),
debugShowCheckedModeBanner: false,
localizationsDelegates: [
i18n,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: i18n.supportedLocales,
),
);
}
I too got similar error and found out that in the newer version of Provider Package (I am using provider: ^6.0.1) you have to pass listen: false whereever you are updating the provider data.
For example on a Tap of button or any Widgets onChanged Event. Below is an example with TextField onChanged callback.
TextField(
onChanged: (newText) {
Provider.of<Data>(context, listen: false).changeString(newText);
},
);
The error seems to point out to these two lines of code to be causing the issue.
#3 _OrderHistoryState._onAfterBuild package:shinier_store/screens/order_history.dart:60
#4 _OrderHistoryState.build. package:shinier_store/screens/order_history.dart:67
The repro you've provided is incomplete and I can only guess the required flag listen: false has been added on _onAfterBuild(). However, the error logs points that var model = Provider.of<OrderModel>(context); inside Widget build() needs to also have the flag. The reason for this flag requirement is explained in the docs.
If you're still having issues, a working minimal repro will be helpful for us to understand why this behavior occurs.
If you are using provider ^6.0.2 then use:
context.read<YourFunction>()
not:
context.watch<YourFunction>()
If you use provider version ^6.0.2,
use:
context.read<TaskData>().addMyTask(
Task(
name: newTaskTitle,
),
);
Instead of this:
context.watch<TaskData>().addMyTask(
Task(
name: newTaskTitle,
),
);
I also found out that use of BlocProvider, even without the listen parameter seems to solve this. But in this case you would be using BLOC for your state management.
In all cases that I use var yyystate = BlocProvider.of(context); I always add the listen parameter.
I was trying to implement a simple login/logout functionality. My scenario is this:
I have 2 pages ( login page and home page), In the main.dart, I am using SharedPreferences to check if a user has already logged in or not if the user is logged in, I set a boolean value as true on click of a button.
The issue I am having is, I have a routeLogin function that I created to choose between Homepage and Landingpage.
And I get this error:
I/flutter ( 9026): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 9026): The following assertion was thrown building MyApp(dirty):
I/flutter ( 9026): type 'Future<dynamic>' is not a subtype of type 'bool'
I/flutter ( 9026):
I/flutter ( 9026): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 9026): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 9026): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 9026): https://github.com/flutter/flutter/issues/new?template=BUG.md
This is my code :
import 'package:credit/src/pages/landing.dart';
import 'package:flutter/material.dart';
import 'package:credit/src/pages/credit/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
bool checkValue;
checkLoginValue () async{
SharedPreferences loginCheck = await SharedPreferences.getInstance();
checkValue = loginCheck.getBool("login");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: routeLogin());
//home: LandingPage());
}
routeLogin()
{
print("Check value");
if (checkValue == null){
return LandingPage();
}
else{
return HomePage();
}
}
}
Please let me know where did I went wrong, I am new to Flutter.
you can use future builder to obtain this behavior easily.
Future<bool> checkLoginValue() async {
SharedPreferences loginCheck = await SharedPreferences.getInstance();
return loginCheck.getBool("login");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FutureBuilder<bool>(
future: checkLoginValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.data == false) {
return LandingPage();
} else {
return HomePage();
}
},
),
);
}
Assuming that your getBool function from loginCheck returns Future,
You are trying to put a Future into a bool.
Change that line to:
checkValue = await loginCheck.getBool("login");
checkValue has a value of Future not a bool.
Future checkValue;
So you could check whether it has returned a value or an error.
routeLogin() {
print("Check value");
checkValue.then((res) {
return LandingPage();
}).catchError(
(e) {
return HomePage();
},
);
}