This simple app works fine in debug mode but not release mode:
main.dart:
import 'package:flutter/material.dart';
void main() {
ErrorWidget.builder = (FlutterErrorDetails details) {
bool inDebug = false;
assert(() { inDebug = true; return true; }());
// In debug mode, use the normal error widget which shows
// the error message:
if (inDebug)
return ErrorWidget(details.exception);
// In release builds, show a yellow-on-blue message instead:
return Container(
alignment: Alignment.center,
child: Text(
'Error! ${details.exception} ${details.stack}',
style: TextStyle(color: Colors.red),
textDirection: TextDirection.ltr,
),
);
};
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) {
var list = <int>[1,2,3,4];
List<int?> nullable = list.sublist(2); // also try "nullable = list"
nullable.add(null);
return Center(
child: Text("nullable $nullable list $list")
);
}
)
);
}
}
Have a look at this snippet:
List<int> list = [1,2,3,4];
List<int?> nullable = list.sublist(2);
nullable.add(null);
print("nullable $nullable list $list");
It prints "nullable [3, 4, null] list [1, 2, 3, 4]"
When you change the code to:
List<int> list = [1,2,3,4];
List<int?> nullable = list;
nullable.add(null);
print("nullable $nullable list $list");
It prints: "nullable [1, 2, 3, 4, null] list [1, 2, 3, 4, null]"
So, in the first example it works as I'd expect. But in Flutter it works like that only in the debug mode. When I create an apk in release mode it throws an exception (Null is not a subtype of 'int').
The second example might be understandable too, but it might be unexpected that we end up with a variable which should allow nulls, but when you try to add a null it throws an error.
So, my question here is why does the debug mode in Flutter work like that and can we somehow change it so that it behaves just like in the release mode.
I'm using VS Code, Flutter 3.7.0
When I run your provided code, I observe the same exception in both debug and release modes.
It appears visually different due to your implementation of ErrorWidget.build, but it is the same exception.
It is expected that this behavior would not differ between debug and release modes.
I suspect the exception is happening for you in debug mode, but it is masked or hidden for some reason. You could try
Check the debug console
Put a breakpoint in the ErrorWidget builder and/or step over the nullable.add(null) in the debugger
Add a print statement after nullable.add(null) to see if it really is proceeding beyond that call
(Side note: You can use the kDebugMode constant to determine debug mode.)
Related
I have a Textfield that allows me to input text, however, when I try to delete a little bit of the text, the program crashes, then my code editor show me the following error page in visual studio code::
error screen
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TextFieldScreen(),
));
}
class TextFieldScreen extends StatefulWidget {
const TextFieldScreen({super.key});
#override
State<TextFieldScreen> createState() => _TextFieldScreenState();
}
class _TextFieldScreenState extends State<TextFieldScreen> {
final TextEditingController _emailTextController =
TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
controller: _emailTextController,
),
],
),
);
}
}
I tried to use flutter clean and now I get the following errors (pretty much everything is on red)
everything goes red
I ran the code and it works flawlessly without so much a warning. So if you are getting error, please upload error log.
You didn't upload error logs if there was an error. It is much needed part in question. Uploaded images didn't provide data needed to solve this question.
Based on your discussion with #Yeasin Sheikh, you need to follow a beginners tutorial which will solve your most basic issues.
Here is an original code of hive.db.dev . I tried to fix it but I could not. I tried to put "?" and "!" but it did not fix it. Here is the code.
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
const darkModeBox = 'darkModeTutorial';
void main() async {
await Hive.initFlutter();
await Hive.openBox(darkModeBox);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: Hive.box(darkModeBox).listenable(),
builder: (context, box, widget) {
var darkMode = box.get('darkMode', defaultValue: false);
return MaterialApp(
themeMode: darkMode ? ThemeMode.dark : ThemeMode.light,
darkTheme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: Switch(
value: darkMode,
onChanged: (val) {
box.put('darkMode', !darkMode);
},
),
),
),
);
},
);
}
}
also here is error message =>
Error: The method 'get' isn't defined for the class 'Object?'.
- 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'get'.
var darkMode = box.get('darkMode', defaultValue: false);
^^^
Error: The method 'put' isn't defined for the class 'Object?'.
- 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'put'.
box.put('darkMode', !darkMode);
^^^
Restarted application in 255ms.
To clear the error you need to tell ValueListenableBuilder what data type its dealing with, in this case it's of type Box.
return ValueListenableBuilder<Box>(...) // adding <Box>
That alone should fix it.
However I would approach this a bit differently so that you're not accessing the Hive box directly from the ValueListenableBuilder. In general I suggest avoiding direct interactions with core stuff like databases directly from the UI.
You can create a ThemeStorage class that a simple boolean isDarkMode. What that changes, it will trigger a rebuild and also stores the updated value. Upon app restart, it will default to whatever is stored. The fields are static because you never need to create a new object of this class.
class ThemeStorage {
static ValueNotifier<bool> isDarkMode =
ValueNotifier(Hive.box(darkModeBox).get('darkMode') ?? false);
static void toggleDarkMode() {
isDarkMode.value = !isDarkMode.value; // this will trigger a rebuild
Hive.box(darkModeBox)
.put('darkMode', isDarkMode.value); // this stores the updated value
}
}
Now your MyApp is simplified and all it cares about is a simple bool. If in the future you want more complex themes you can define them in the ThemeStorage class and instead of building from a bool you could build off a ThemeData variable.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// be sure to always put data type here
return ValueListenableBuilder<bool>(
valueListenable: ThemeStorage.isDarkMode,
builder: (_, isDarkMode, __) {
// here isDarkMode is a bool and not an Object because of inserting bool above
return MaterialApp(
theme: isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
body: Center(
child: Switch(
value: isDarkMode,
onChanged: (val) => ThemeStorage.toggleDarkMode(),
),
),
),
);
},
);
}
}
In my Flutter app, I'm trying to use the Dart spread operator (ie-...) at the else section of a ternary operator in the build() method of one of my widgets.
ie-
class SearchCriteriaState extends State<SearchCriteria> {
List<SearchParameters> listSearchParam = [];
Widget build(BuildContext context) {
return Column(
children: [
Text('hey there'),
listSearchParam.isEmpty
? Text ('Empty
: ...listSearchParam.map((param)=>Text(param.toString()).toList(),
],
);
}
}
The code above will not compile, getting the error below with the spread statement underlined in red.
Expected to find ','.dart(expected_token)
If I replace the whole ternary with just the spread statement, it will compile and run (except it doesn't deal with the edge condition where the list is empty).
Any ideas?
The workaround I'm using currently is to wrap the spread statement in another Column() which works, but is fugly ('scuse my French).
I had already searched for similar issues and the closest I could find was also a problem with using the spread operator in a ternary but in the context of webpack which I'm assuming is with React.
All suggestions much appreciated.
/Joselito
Please use if else.
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<String> texts = <String>['1', '2', '3'];
final bool test = false;
return MaterialApp(
home: Scaffold(
body: Column(
children: [
const Text('asdf'),
if (test)
const Text('test is TRUE')
else
...texts.map((e) => Text(e)).toList(),
],
),
),
);
}
}
Where do I run initialisation code when starting a flutter app?
void main() {
return runApp(MaterialApp(
title: "My Flutter App",
theme: new ThemeData(
primaryColor: globals.AFI_COLOUR_PINK,
backgroundColor: Colors.white),
home: RouteSplash(),
));
}
If I want to run some initialisation code to, say fetch shared preferences, or (in my case) initialise a package (and I need to pass in the the BuildContext of the MaterialApp widget), what is the correct way to do this?
Should I wrap the MaterialApp in a FutureBuilder? Or is there a more 'correct' way?
------- EDIT ---------------------------------------------------
I have now placed the initialisation code in RouteSplash() widget. But since I required the BuildContext of the app root for the initialisation, I called the initialisation in the Widget build override and passed in context.ancestorInheritedElementForWidgetOfExactType(MaterialApp). As I don't need to wait for initialisation to complete before showing the splash screen, I haven't used a Future
One simple way of doing this will be calling the RouteSplash as your splash screen and inside it perform the initialization code as shown.
class RouteSplash extends StatefulWidget {
#override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
bool shouldProceed = false;
_fetchPrefs() async {
await Future.delayed(Duration(seconds: 1));// dummy code showing the wait period while getting the preferences
setState(() {
shouldProceed = true;//got the prefs; set to some value if needed
});
}
#override
void initState() {
super.initState();
_fetchPrefs();//running initialisation code; getting prefs etc.
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: shouldProceed
? RaisedButton(
onPressed: () {
//move to next screen and pass the prefs if you want
},
child: Text("Continue"),
)
: CircularProgressIndicator(),//show splash screen here instead of progress indicator
),
);
}
}
and inside the main()
void main() {
runApp(MaterialApp(
home: RouteSplash(),
));
}
Note: It is just one way of doing it. You could use a FutureBuilder if you want.
To run code at startup, put it in your main.dart. Personally, that's the way I do it, to initialise lists etc.
I am using Flutter 1.2.1 in the Stable branch. To illustrate my problem imagine I have pages A and B. A navigates to B using Navigator.push and B navigates back to A using Navigator.pop. Both are stateful widgets.
When I navigate from A to B and then pop back to A everything is fine and A keeps its state. However, if I navigate from A to B, tap a textfield in B opening the keyboard, then close the keyboard and pop back to A, A's entire state is refreshed and the initState() method for A is called again. I verified this by using print statements.
This only happens when I open the keyboard before popping back to A. If I navigate to B, then immediately navigate back to A without interacting with anything then A keeps its state and is not re-initialized.
From my understanding the build method is called all the time but initState() should not get called like this. Does anyone know what is going on?
After much trial and error I determined the problem. I forgot that I had setup a FutureBuilder for the / route in my MaterialApp widget. I was passing a function call that returns a future to the future parameter of the FutureBuilder constructor rather than a variable pointing to a future.
So every time the routes got updated a brand new future was being created. Doing the function call outside of the MaterialApp constructor and storing the resulting future in a variable, then passing that to the FutureBuilder did the trick.
It doesn't seem like this would be connected to the weird behavior I was getting when a keyboard opened, but it was definitely the cause. See below for what I mean.
Code with a bug:
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureFun(), //Bug! I'm passing a function that returns a future when called. So a new future is returned each time
builder: (context, snapshot) {
...
}
...
}
...
}
Fixed Code:
final futureVar = futureFun(); //calling the function here instead and storing its future in a variable
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureVar, //Fixed! Passing the reference to the future rather than the function call
builder: (context, snapshot) {
...
}
...
}
...
}
did you use AutomaticKeepAliveClientMixin in "A" widget ?
if you don't , see this https://stackoverflow.com/a/51738269/3542938
if you already use it , please give us a code that we can test it directly into "main.dart" to help you
Yup, happened to me, perhaps it's much better to wrap the FutureBuilder itu a PageWidget, and make it singleton
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => PageWidget() // wrap it by PageWidget
...
}
...
}
class PageWidget extends StatelessWidget {
static final _instance = PageWidget._internal(); // hold instance
PageWidget._internal(); // internal consturctor
factory PageWidget() {
return _instance; // make it singleton
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>( ... );
}
}
I got a solution, I was initialising variables in the constructor of the superclass. I removed it and worked!
I just removed the FutureBuilder from the home of MaterialApp and changed the MyApp into a Stateful widget and fetched the requisite info in the initState and called setState in the .then(); of the future and instead of passing multiple conditions in the home of MaterialApp, I moved those conditions to a separate Stateful widget and the issue got resolved.
initState:
#override
void initState() {
// TODO: implement initState
// isSignedIn = SharedPrefHelper.getIsSignedIn();
getIsSignedInFromSharedPreference().then((value) {
setState(() {
isSignedInFromSharedPref = value ?? false;
if (isSignedInFromSharedPref) {
merchantKey = LocalDatabase.getMerchantKeyWithoutAsync();
}
isLoadingSharedPrefValue = false;
});
});
super.initState();
}
Future<bool?> getIsSignedInFromSharedPreference() async {
return SharedPrefHelper.getIsSignedIn();
}
MaterialApp (now):
MaterialApp(
title: 'Loveeatry POS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(
isLoadingSharedPrefValue: isLoadingSharedPrefValue,
isSignedInFromSharedPref: isSignedInFromSharedPref,
merchantKey: merchantKey,
),
),
Home:
class Home extends StatelessWidget {
final bool isLoadingSharedPrefValue;
final bool isSignedInFromSharedPref;
final String merchantKey;
const Home({
Key? key,
required this.isLoadingSharedPrefValue,
required this.isSignedInFromSharedPref,
required this.merchantKey,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (!isLoadingSharedPrefValue) {
if (isSignedInFromSharedPref) {
return const Homepage(
shouldLoadEverything: true,
);
} else if (merchantKey.isNotEmpty) {
return LoginPage(merchantKey: merchantKey);
} else {
return const AddMerchantKeyPage();
}
} else {
return loading(context);
}
}
}
P.S.: If you need any more info, please leave a comment.