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.
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"),
),
);
}
I am using the library Provider like this:
runApp(
LocalizedApp(
delegate,
MyApp(
localizedValues: {},
),
),
);
and then:
class MyApp extends StatelessWidget {
final Map<String, Map<String, String>> localizedValues;
MyApp({required this.localizedValues});
#override
Widget build(BuildContext context) {
final localizationDelegate = LocalizedApp.of(context).delegate;
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MyRents()),
],
child: MaterialApp(
initialRoute: initialRoute,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
localizationDelegate
],
This is my provider:
class MyRents extends ChangeNotifier {
// LIST OF VARS NOT USED AT THIS POINT
bool loadingUserInfo = false;
var language;
// List list = rentsList;
List list = [];
int currentPortfolioIndex = 0;
int currenRentIndex = 0;
var clonedRent;
List newGalleryImages = []; // local gallery images that we haven't pushed yet
SharedPreferences? prefs;
String viewMode = 'multi';
bool galleryScrollView = true;
PageController portfolioController = PageController(initialPage: 0);
MyUser? user; // TODO: move this to a separate provider
List<ProductDetails> products = [];
List<PurchaseDetails> purchases = [];
bool showAds = false;
Map companyInfo = {};
List withholdings = [];
List<QueryDocumentSnapshot> taxes = [];
// END LIST
// MyRents(); // TODO: check null (this was working in Flutter 1 but not now so it might be related to the error)...
final MyRents myRents = MyRents(); // ...and this is the Flutter 2 version
updateUI() {
notifyListeners();
}
}
final MyRents myRents = MyRents();
This was working well in Flutter 1 but I'm getting this error now in Flutter 2:
Reassembling main.dart$main-2339422228391703 failed:
ext.flutter.reassemble: (-32000) Server error {"exception":"Bad state:
Tried to read a provider that threw during the creation of its
value.\nThe exception occurred during the creation of type
MyRents.","stack":"#0 _CreateInheritedProviderState.value
(package:provider/src/inherited_provider.dart:661:7)\n#1
_InheritedProviderScopeElement.reassemble (package:provider/src/inherited_provider.dart:367:60)\n#2
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#3
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#4
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#5
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#6
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#7
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#8
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#9
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#10
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#11
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#12
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#13
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#14
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#15
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#16
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#17
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#18
ComponentElement.visitChildren
(package:flutter/src/widgets/framework.dart:4549:14)\n#19
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#20
StatefulElement.reassemble
(package:flutter/src/widgets/framework.dart:4624:11)\n#21
Element.reassemble.
(package:flutter/src/widgets/framework.dart:3072:13)\n#22
RenderObjectToWidgetElement.visitChildren
(package:flutter/src/widgets/binding.dart:1139:14)\n#23
Element.reassemble
(package:flutter/src/widgets/framework.dart:3071:5)\n#24
BuildOwner.reassemble
(package:flutter/src/widgets/framework.dart:2892:12)\n#25
WidgetsBinding.performReassemble
(package:flutter/src/widgets/binding.dart:951:19)\n#26
BindingBase.lockEvents
(package:flutter/src/foundation/binding.dart:298:41)\n#27
BindingBase.reassembleApplication
(package:flutter/src/foundation/binding.dart:338:12)\n#28
BindingBase.registerSignalServiceExtension.
(package:flutter/src/foundation/binding.dart:375:23)\n#29
BindingBase.registerSignalServiceExtension.
(package:flutter/src/foundation/binding.dart:374:17)\n#30
BindingBase.registerServiceExtension.
(package:flutter/src/foundation/binding.dart:597:32)\n\n","method":"ext.flutter.reassemble"}
and this other one:
======== Exception caught by Flutter framework ===================================================== The following StateError was thrown during a service extension callback for
"ext.flutter.reassemble": Bad state: Tried to read a provider that
threw during the creation of its value. The exception occurred during
the creation of type MyRents.
Comment this out
final MyRents myRents = MyRents(); and also use this
ChangeNotifierProvider<MyRents>(create: (_) => MyRents())
I'm trying to develop "BLE Control App" with using flutter_Blue.
I added a tab bar so I want to Maintain Bluetooth State "Connect".
so I'm trying to use Provider, To set connection state but I have an error like this.
**======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for BluetoothProvider:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<BluetoothProvider> 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.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<BluetoothProvider>
value: Instance of 'BluetoothProvider'
listening to value
The widget which was currently being built when the offending call was made was: Consumer<BluetoothProvider>
dirty
dependencies: [_InheritedProviderScope<BluetoothProvider>]
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4138:11)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4153:6)
#2 _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:531:5)
#3 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:243:25)
#4 BluetoothProvider.startScan (package:flutter_joystick/provider/bluetooth_provider.dart:46:5)
...
The BluetoothProvider sending notification was: Instance of 'BluetoothProvider'**
this is my bluetooth provider code
class BluetoothProvider with ChangeNotifier{
final String SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
final String CHARACTERISTIC_UUID="0000ffe1-0000-1000-8000-00805f9b34fb";
final String TARGET_DEVICE_NAME="HMSoft";
FlutterBlue flutterBlue = FlutterBlue.instance;
StreamSubscription<ScanResult> scanSubScription;
BluetoothDevice targetDevice;
BluetoothCharacteristic targetCharacteristic;
BluetoothState bluetoothState;
String connectionText="";
String joystick="";
startScan(){
connectionText="Start Scanning";
scanSubScription = flutterBlue.scan().listen((scanResult){
if(scanResult.device.name==TARGET_DEVICE_NAME){
print("Device Found");
stopScan();
connectionText="Found Target Device";
targetDevice = scanResult.device;
}
}, onDone: () => stopScan());
notifyListeners();
}
stopScan(){
scanSubScription?.cancel();
scanSubScription=null;
notifyListeners();
}
connectToDevice() async{
if(targetDevice==null) return;
connectionText = "Device Connecting";
await targetDevice.connect();
print("Device Connected");
connectionText="Device Connected";
discoverServices();
notifyListeners();
}
disconnectFromDevice(){
if(targetDevice==null) return;
targetDevice.disconnect();
connectionText="Device Disconnected";
notifyListeners();
}
discoverServices() async{
if(targetDevice==null) return;
List<BluetoothService> services = await targetDevice.discoverServices();
services.forEach((service) {
if(service.uuid.toString() == SERVICE_UUID){
service.characteristics.forEach((characteristc) {
if (characteristc.uuid.toString() == CHARACTERISTIC_UUID) {
targetCharacteristic = characteristc;
writeData("Connect Complete!\r\n");
connectionText = "All Ready with ${targetDevice.name}";
}
});
}
}
);
notifyListeners();
}
writeData(String data) async{
if(targetCharacteristic==null) return;
List<int> bytes = utf8.encode(data);
await targetCharacteristic.write(bytes);
notifyListeners();
}
}
Funny, the Bluetooth connection is progressing, but the error written above keeps coming up through the console window.
The first page of the Tab Bar is the joystick page, and Bluetooth is connected due to an error, but the joystick is not working.
Here is Joystick code
class JoyPad extends StatefulWidget {
#override
_JoyPadState createState() => _JoyPadState();
}
class _JoyPadState extends State<JoyPad> {
BluetoothProvider _bluetoothProvider;
#override
Widget build(BuildContext context) {
_bluetoothProvider = Provider.of<BluetoothProvider>(context,listen:false);
return Consumer<BluetoothProvider>(
builder:(context,provider,child) {
_bluetoothProvider.startScan();
return Scaffold(
appBar: AppBar(
title: Text(_bluetoothProvider.connectionText),
backgroundColor: Colors.indigoAccent,
actions: <Widget>[
IconButton(
icon: Icon(Icons.bluetooth), iconSize: 30,
onPressed: () {
_bluetoothProvider.connectToDevice();
print(_bluetoothProvider.bluetoothState.toString());
},
),
IconButton(
icon: Icon(Icons.bluetooth_disabled), iconSize: 30,
onPressed: () {
_bluetoothProvider.disconnectFromDevice();
print(_bluetoothProvider.bluetoothState.toString());
}),
],
),
body: joystickWidget(),
);
});
}
}
Additionally, the provider does not "setState" so I try to display connection text according to the status change on the App Bar, but it is not possible.
I would also appreciate it if you could tell me how to solve it.
You are actually encountering this error because you try to rebuild the widget tree while it's being build.
Your call on _bluetoothProvider.startScan();in your Consumer's builder method will call the notifyListeners method which actually tries to rebuild the tree while it's being build, thus that exception will be thrown.
WHY?
The Consumer widget is actually listening to changes on your BluetoothProvider; so when you call the notifyListeners on the BluetothProvider class, the Consumer tries to rebuild itself, which is not authorized.
A solution would be to first build the tree, and then call the startScan method.
You could try this:
Provider Code
class BluetoothProvider with ChangeNotifier{
final String SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
final String CHARACTERISTIC_UUID="0000ffe1-0000-1000-8000-00805f9b34fb";
final String TARGET_DEVICE_NAME="HMSoft";
FlutterBlue flutterBlue = FlutterBlue.instance;
StreamSubscription<ScanResult> scanSubScription;
BluetoothDevice targetDevice;
BluetoothCharacteristic targetCharacteristic;
BluetoothState bluetoothState;
String connectionText="";
String joystick="";
startScan() {
connectionText="Start Scanning";
scanSubScription = flutterBlue.scan().listen((scanResult){
if(scanResult.device.name==TARGET_DEVICE_NAME){
print("Device Found");
stopScan();
connectionText="Found Target Device";
targetDevice = scanResult.device;
}
}, onDone: () => stopScan());
notifyListeners();
}
stopScan() {
scanSubScription?.cancel();
scanSubScription=null;
notifyListeners();
}
connectToDevice() async{
if(targetDevice==null) return;
connectionText = "Device Connecting";
await targetDevice.connect();
print("Device Connected");
connectionText="Device Connected";
discoverServices();
notifyListeners();
}
disconnectFromDevice(){
if(targetDevice==null) return;
targetDevice.disconnect();
connectionText="Device Disconnected";
notifyListeners();
}
discoverServices() async {
if(targetDevice==null) return;
List<BluetoothService> services = await targetDevice.discoverServices();
services.forEach((service) {
if(service.uuid.toString() == SERVICE_UUID){
service.characteristics.forEach((characteristc) {
if (characteristc.uuid.toString() == CHARACTERISTIC_UUID) {
targetCharacteristic = characteristc;
writeData("Connect Complete!\r\n");
connectionText = "All Ready with ${targetDevice.name}";
}
});
}
});
notifyListeners();
}
writeData(String data) async{
if(targetCharacteristic==null) return;
List<int> bytes = utf8.encode(data);
await targetCharacteristic.write(bytes);
notifyListeners();
}
}
Widget code
class JoyPad extends StatefulWidget {
#override
_JoyPadState createState() => _JoyPadState();
}
class _JoyPadState extends State<JoyPad> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// The code in this block will be executed after the build method
context.read<BluetoothProvider>().startScan();
});
}
#override
Widget build(BuildContext context) {
return Consumer<BluetoothProvider>(
builder:(context,provider,child) {
return Scaffold(
appBar: AppBar(
title: Text(_bluetoothProvider.connectionText),
backgroundColor: Colors.indigoAccent,
actions: <Widget>[
IconButton(
icon: Icon(Icons.bluetooth), iconSize: 30,
onPressed: () {
_bluetoothProvider.connectToDevice();
print(_bluetoothProvider.bluetoothState.toString());
},
),
IconButton(
icon: Icon(Icons.bluetooth_disabled), iconSize: 30,
onPressed: () {
_bluetoothProvider.disconnectFromDevice();
print(_bluetoothProvider.bluetoothState.toString());
},
),
],
),
body: joystickWidget(),
);
});
}
}
}
context.read<BluetoothProvider>().startScan(); is a shortcut for Provider.of<BluetoothProvider>(context, listen: false).startScan() : it basically does the same thing.
I use https://pub.dev/packages/localstorage to create local storage in my Flutter app.
I created some method to use with this local storage in this 'Event' widget
class Event extends StatefulWidget {
final eventInfo;
Event({Key key, List eventInfo})
: this.eventInfo = eventInfo,
super(key: key);
#override
_EventState createState() => _EventState();
}
class FavName {
String eventName;
FavName({this.eventName});
get favEventName {
return eventName;
}
toJSONEncodable() {
Map<String, dynamic> m = new Map();
m['Name'] = eventName;
return m;
}
}
class FavList {
List<FavName> favNameList;
FavList() {
favNameList = new List();
}
toJSONEncodable() {
return favNameList.map((item) {
return item.toJSONEncodable();
}).toList();
}
}
class _EventState extends State<Event> {
final FavList list = new FavList();
final LocalStorage storage = new LocalStorage('favList');
addItem(String eventName) {
setState(() {
final item = new FavName(eventName: eventName);
list.favNameList.add(item);
_saveToStorage();
});
}
deleteItem(String eventName) {
setState(() {
final item = new FavName(eventName: eventName);
list.favNameList.remove(item);
_saveToStorage();
});
}
_saveToStorage() {
storage.setItem('favList', list.toJSONEncodable());
}
. . .
So I wanted to add a String into my local storage with a button.
Here is my code for the button.
child: IconButton(
icon: Icon(
Icons.star,
color: _iconColors,
),
onPressed: () {
print(name);
(storage.getItem(name)) ? deleteItem(name)
: addItem(name);
setState(() {
if (storage.getItem(name) == null) {
_iconColors = Colors.grey;
} else {
_iconColors = Colors.yellow;
}
});
})
But when I tap on the button, I get this error
I/flutter (27489): 2-Art Event //Print the string that I want to add to the local storage
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
Failed assertion: boolean expression must not be null
When the exception was thrown, this was the stack
#0 _EventState.buildInfo.<anonymous closure>
package:lx_building/screens/event.dart:190
#1 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:772
#2 _InkResponseState.build.<anonymous closure>
package:flutter/…/material/ink_well.dart:855
#3 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:182
#4 TapGestureRecognizer.handleTapUp
package:flutter/…/gestures/tap.dart:522
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#1f697
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(324.7, 264.3)
finalLocalPosition: Offset(32.4, 9.7)
button: 1
sent tap down
════════════════════════════════════════════════════════════════════════════════
How can I fix this?
storage.getItem(name) returns the data stored which generally is a Map.
In your condition you must use Boolean. You can just test that your return isn't null :
(storage.getItem(name) != null) ? deleteItem(name)
: addItem(name);
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