I currently have a TextField embedded inside a SimpleDialog for entering information. The information entered needs to be validated before being used. If this validation fails, I want to show an error message using the TextField's ErrorText field. This validation occurs when the user presses a "save" button.
On fail, I update the text's value from null to "Error!". I know this is being done correctly because if I exit the SimpleDialog and then go back into the dialog, the text is updated. Clearly this is an issue with state.
Here is my code for the OnPressed method for the save button:
onPressed: () {
if (!checkDevEUICorrectness()) {
setState(() {
devEUIErrorText = "Error!";
});
}
else {
setState((){
devEUIErrorText = null;
});
}
},
And here a brief bit of code for the TextField:
new TextField(
controller: devEUIController,
decoration: new InputDecoration(
errorText: devEUIErrorText,
// This is the save button whose code is above
icon: new IconButton(...implementation...),
),
),
How should I properly be updating the state for the text? I don't see a difference in what I am doing compared to the many guides I have looked at online.
The code you posted dosen't help with anything but i think ik what might be the error.
When we open a SimpleDialog on a button click and try to perform something inside it.
Take care the SimpleDialog here acts as a stateless widget .
when we write
showDialog(
context: context,
builder: (BuildContext context){
return SimpeDialog(
.... // the code for deigning the simpleDialog
);
});
What you have to do is inspite of doing this .
you need to do something like this :-
showDialog(
context: context,
builder: (BuildContext context){
return DialogDemo();
});
in that DialogDemo create a stateful widget and inside that widget copy paste all the code under your main build function.
class DialogDemo extends StatefulWidget {
DialogDemoState createState() => new DialogDemoState();
}
class DialogDemoState extends State<DialogDemo> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SimpleDialog(
.... // the code for deigning the simpleDialog
);
}
}
Related
I want to show an AlertDialog when a http get fails. The function showDialog (https://api.flutter.dev/flutter/material/showDialog.html) has the parameter "#required BuildContext context", but I want to call the AlertDialog from my async function getNews(), which hasn't a context value.
By analogy with Java, where I use null for dialog without an owner, I tried to put context value to null, but it is not accepted.
This is my code:
Future<dynamic> getNews() async {
dynamic retVal;
try {
var response = await http.get(url));
if (response.statusCode == HttpStatus.ok) {
retVal = jsonDecode(response.body);
}
} catch (e) {
alertDlg(?????????, 'Error', e.toString());
}
return
retVal;
}
static Future<void> alertDlg(context, String titolo, String messaggio) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(titolo),
...
);
}
Solution without third-party libraries or storing BuildContext:
final navigatorKey = GlobalKey<NavigatorState>();
void main() => runApp(
MaterialApp(
home: HomePage(),
navigatorKey: navigatorKey, // Setting a global key for navigator
),
);
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Center(
child: Text('test')
)
),
floatingActionButton: FloatingActionButton(
onPressed: showMyDialog, // Calling the function without providing it BuildContext
),
);
}
}
void showMyDialog() {
showDialog(
context: navigatorKey.currentContext,
builder: (context) => Center(
child: Material(
color: Colors.transparent,
child: Text('Hello'),
),
)
);
}
After setting a global key for navigator (navigatorKey parameter of MaterialApp class), its current state becomes accessible from any part of the code. We can pass its context to the showDialog function. Make sure not to use it before the first frame is built, otherwise the state will be null.
Basically, dialogs are just another type of routes like MaterialPageRoute or CupertinoPageRoute - they are all derived from the ModalRoute class and their instances are pushed into NavigatorState. Context is only needed to get the current navigator's state. A new route can be pushed without having a context if we have a global navigator key:
navigatorKey.currentState.push(route)
Unfortunately, _DialogRoute class (...\flutter\lib\src\widgets\routes.dart) used in showDialog function is private and unaccessible, but you make your own dialog route class and push it into the navigator's stack.
UPDATE: Navigator.of method has been updated and it's no longer needed to pass subtree context.
The dialog only needs the context to access it through an inherited navigateState.
You can make your own modified dialog, or use my lib to do this.
https://pub.dev/packages/get
With it you can open dialog from anywhere in your code without context by doing this:
Get.dialog(SimpleDialog());
Catch the exception where you make the getNews call if you use await, else use the catchError property of the Future.
So you need a BuildContext to create a dialog but you don't have access to it. That's a common problem, you can refer to this StackOverflow question for one of the approaches to solve it (create a static dialog and show it from wherever you need).
Another approach you may consider is to pass the context when creating an async method or object as an argument. Make sure you null it when you're done.
Or you can make a flag (boolean) which becomes 'true' under a certain condition, and in one of the build() methods you always check that flag, and if it's 'true' - do your thing (show a dialog for instance).
Easiest way to show alert anywhere:
Use this package link and enter these lines wherever you want to show alert:
QuickAlert.show( context: context, type: QuickAlertType.error, text: 'Error Message');
I'm new to Flutter and confused about how InheritedWidget works with routes. I'm using an SQLite database with the sqflite library. Basically, what I'm trying to achieve is, when my app is launched, I want all widgets that don't require the database to show right away. For instance, the bottomNavigationBar of my Scaffold doesn't need the database but the body does. So I want the bottomNavigationBar to show right away, and a CircularProgressIndicator to be shown in the body. Once the database is open, I want the body to show content loaded from the database.
So, in my attempt to achieve this, I use FutureBuilder before my Scaffold to open the database. While the Future is not completed, I pass null for the drawer and a CircularProgressBar for the body, and the bottomNavigationBar as normal. When the Future completes, I wrap the drawer and body (called HomePage) both with their own InheritedWidget (called DataAccessor). This seems to work, as I can access the DataAccessor in my HomePage widget. But, when I use the Navigator in my drawer to navigate to my SettingsScreen, my DataAccessor is not accessible and returns null.
Here's some example code, not using a database but just a 5 second delayed Future:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
future: Future.delayed(Duration(seconds: 5)),
builder: (context, snapshot) {
Widget drawer;
Widget body;
if (snapshot.connectionState == ConnectionState.done) {
drawer = DataAccessor(
child: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Settings"),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen()))
)
]
)
)
);
body = DataAccessor(child: HomePage());
}
else {
drawer = null;
body = Center(child: CircularProgressIndicator());
}
return Scaffold(
drawer: drawer,
body: body,
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Container(), title: Text("One")),
BottomNavigationBarItem(icon: Container(), title: Text("Two"))
]
)
);
}
)
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS NOT null here
print("HomePage: ${dataAccessor == null}");
return Text("HomePage");
}
}
class SettingsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS null here
print("SettingsScreen: ${dataAccessor == null}");
return Text("SettingsScreen");
}
}
class DataAccessor extends InheritedWidget {
DataAccessor({Key key, Widget child}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
static DataAccessor of(BuildContext context) => context.inheritFromWidgetOfExactType(DataAccessor);
}
It's possible I'm doing things wrong. Not sure how good of practice storing widgets in variables is. Or using the same InheritedWidget twice? I've also tried wrapping the entire Scaffold with my DataAccessor (and having the database as null while it is loading), but the issue still remains where I can't get my DataAccessor in my SettingsScreen.
I've read that a possible solution is to put my InheritedWidget before the MaterialApp but I don't want to resort to this. I don't want a whole new screen to show while my database is opening, I want my widgets that don't need the database to be shown. This should be possible somehow.
Thanks!
The solution in the last paragraph is what you need. The MaterialApp contains the Navigator which manages the routes, so for all of your routes to have access to the same InheritedWidget that has to be above the Navigator, i.e. above the MaterialApp.
Use Remi's method and you end up with a widget tree like this:
MyApp (has the static .of() returning MyAppState)
MyAppState, whose build returns _MyInherited(child: MaterialApp(...)) and whose initState starts loading the database, calling setState when loaded.
When building your home page you have access to MyAppState via .of, so can ascertain whether the database has loaded or not. If it has not, just build the database independent widgets; if it has, build all the widgets.
I am using sqflite database to save user list.
I have user list screen, which shows list of user and it has a fab button,
on click of fab button, user is redirected to next screen where he can add new user to database.
The new user is properly inserted to the database
but when user presses back button and go backs to user list screen,
the newly added user is not visible on the screen.
I have to close the app and reopen it,then the newly added user is visible on the screen.
I am using bloc pattern and following is my code to show user list
class _UserListState extends State<UserList> {
UserBloc userBloc;
#override
void initState() {
super.initState();
userBloc = BlocProvider.of<UserBloc>(context);
userBloc.fetchUser();
}
#override
void dispose() {
userBloc?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pushNamed("/detail");
},
child: Icon(Icons.add),
),
body: StreamBuilder(
stream: userBloc.users,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data != null) {
return ListView.builder(
itemBuilder: (context, index) {
return Dismissible(
key: Key(snapshot.data[index].id.toString()),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
userBloc.deleteParticularUser(snapshot.data[index]);
},
child: ListTile(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => UserDetail(
user: snapshot.data[index],
)));
},
title: Text(snapshot.data[index].name),
subtitle:
Text("Mobile Number ${snapshot.data[index].userId}"),
trailing:
Text("User Id ${snapshot.data[index].mobileNumber}"),
),
);
},
itemCount: snapshot.data.length,
);
}
},
),
);
}
}
Following is my bloc code
class UserBloc implements BlocBase {
final _users = BehaviorSubject<List<User>>();
Observable<List<User>> get users => _users.stream;
fetchUser() async {
await userRepository.initializeDatabase();
final users = await userRepository.getUserList();
_users.sink.add(users);
}
insertUser(String name,int id,int phoneNumber) async {
userRepository.insertUser(User(id, name, phoneNumber));
fetchUser();
}
updateUser(User user) async {
userRepository.updateUser(user);
}
deleteParticularUser(User user) async {
userRepository.deleteParticularUser(user);
}
deleteAllUser() {
return userRepository.deleteAllUsers();
}
#override
void dispose() {
_users.close();
}
}
As Remi posted answer saying i should try BehaviorSubject and ReplaySubject which i tried but it does not help. I have also called fetchUser(); inside insertUser() as pointed in comments
Following is the link of the full example
https://github.com/pritsawa/sqflite_example
Follow up from the comments, it seems you don't have a single instance of your UsersBloc in those two pages. Both the HomePage and UserDetails return a BlocProvider which instantiate a UsersBloc instance. Because you have two blocs instances(which you shouldn't have) you don't update the streams properly.
The solution is to remove the BlocProvider from those two pages(HomePage and UserDetail) and wrap the MaterialApp widget in it to make the same instance available to both pages.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: UserBloc(),
child:MaterialApp(...
The HomePage will be:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return UserList(),
);
}
}
Remove the BlocProvider from UserDetail as well.
In the UsersBloc then call fetchUser() inside the insertUser() method after the user insertion, to requery the database and update the users stream.
Also as Rémi Rousselet said, use one of the subjects that return previous values.
The issue is that you're using a PublishSubject.
When a new listener subscribes to a PublishSubject, it does not receive the previously sent value and will only receive the next events.
The easiest solution is to use a BehaviorSubject or a ReplaySubject instead. These two will directly call their listener with the latest values.
#override
void initState() {
super.initState();
userBloc = BlocProvider.of<UserBloc>(context);
userBloc.fetchUser();
}
The problem is that you have called the userBloc.fetchUser() function in the initState of the page.
Bloc stream emits whenever a new data is added to it and the userBloc.fetchUser() function does exactly that, it adds the userList that you fetch from the Sqflite database.
Whenever you come back to the userlist screen from add user screen, init function is NOT called. It is only called when the userlist screen is created, that is, whenever you push it to the navigation stack.
The workaround is to call userBloc.fetchUser() whenever your StreamBuilder's snapshot data is null.
...
if (!snapshot.hasData) {
userBloc.fetchUser();
return Center(
child: CircularProgressIndicator(),
);
}
...
I got a List, called savedListsList, that contains a list of Objects. When the app starts this list is empty. When the users clicks on a PopupMenuItem (depicted in the case 0: case), data is loaded from an internal database and after that, a new screen shows up where the data is shown (basically a StatelessWidget that contains a ListView.builder with a certain amount of rows).
Every row consists of a GestureDetector that listens for left and right swipes.
On every of these swipes updateSaveListItem is called. According to the beingEdited attribute the appearance of the row inside the before mentioned ListView.builder is changed.
I know that the swipes trigger the attribute to change accordingly, since I printed that out.
Unfortunately the change in updateSavedListItem doesn't cause a re-render of the current screen. When I hot-reload the app the change of beingEdited is reflected.
I'm pretty sure that I can get this to work, when I turn the
widget that surrounds the ListView.builder into a StatefulWidget.
But my question is: can I trigger a re-render of the current page with the architecture I described? Since I want to keep as much of my state management as I can inside my _MainPage class.
class _MainPage extends State<MainPage> {
List<SavedListObj> savedListsList = List();
#override
void initState() {
savedListsList = List();
}
void updateSavedListItem(int id, String action, String newName) {
setState(() {
switch (action) {
case 'beingEditedStart':
savedListsList[id].beingEdited = true;
break;
case 'beingEditedEnd':
savedListsList[id].beingEdited = false;
break;
case 'delete':
break;
case 'edit':
break;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
PopupMenuButton(
onSelected: (value) {
switch (value) {
case 0:
DB db = DB();
db.getListNames().then((queryResult) {
savedListsList.clear();
setState(() {
savedListsList.addAll(queryResult);
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SavedListsPage(
savedListsList,
updateSavedListItem,
),
),
);
break;
}
},
),
],
),
);
}
}
No you cannot do that! you have to use Stateful widget when you have to update the state of your app. For futher reference you can refer this link.
I'm currently working on a Flutter version of an android application and now want to implement the first launch page. It should only be displayed on the very first launch of the app after it has been installed on a device. I figured out how to do it for the android app, but I only have a basic knowledge of Flutter, so I'm asking this question.
Does anyone know how to do this in Flutter in a simple way?
Thanks in advance for your help!
You could do this by having a separate route for your intro/app:
void main() {
runApp(new MaterialApp(
home: new MyIntroPage(),
routes: <String, WidgetBuilder> {
'/app': (BuildContext context) => new MyAppPage()
},
));
}
In your intro page you could check a flag for whether the user has seen it (see here for some ideas on persisting data) and if so, just navigate straight to the app (for example like the _completeLogin function here)
You could make the Splash screen in Dart but is not recommend because it can bore your users (you will use a fixed duration time) , so in my personal opinion I will go for a native splash screen in each platform.
Android
Modify the file launch_background.xml
Uncomment the bitmap item and
put any item that you have in your mipmap or drawable folder
Change the color of your background in android:drawable item
iOS
Add your custom images into the Assets.xcassets folder
Modify the file LaunScreen.storyboard, change the background color, add and imageview, etc.
I created a post with all of the details about the Splash Screen in Flutter, you can take a look : https://medium.com/#diegoveloper/flutter-splash-screen-9f4e05542548
First-time view Page with user input, It's sample code, you can get idea
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:reborn_next_job02/ui/LoginScreen.dart';
import 'package:reborn_next_job02/ui/splashScreen.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Splash extends StatefulWidget {
#override
SplashState createState() => new SplashState();
}
class SplashState extends State<Splash> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new LoginScreen()));
} else {
prefs.setBool('seen', true);
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new urlScreen()));
}
}
#override
void initState() {
super.initState();
new Timer(new Duration(seconds: 10), () {
checkFirstSeen();
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text('Loading...'),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Hello'),
),
body: new Center(
child: new Text('This is the second page'),
),
);
}
}
class IntroScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('This is the intro page'),
new MaterialButton(
child: new Text('Gogo Home Page'),
onPressed: () {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new Home()));
},
)
],
),
),
);
}
}
I don't really Know the exact Logic but This is How I would implement it,
I will be just explaining you the logic the code below is not syntactically correct
lets call your one time view page as intro page
I will maintain an integer variable in that intro page
int counter;
When for the first Time the intro page loads
I will initialize
counter = sharedPreferencevalue // defaults to 0 when App is fresh installed
and check
if counter==0
if yes
load the intro page
counter++;
store counter in SharedPreference; //overwrite the default value
else
go to Home
simply increment the variable and store the value of that variable locally using SharedPreference
for those who don't know about Shared Preferences, it allows a Flutter application (Android or iOS) to save settings, properties, data in the form of key-value pairs that will persist even when the user closes the application.
hope this Helps :)
Doesn't help you in remove back button but i think it will help you in making splash screen I developed a package the Link
Please find the repo.
If you want to take a look at Flutter you can find some good examples at our company’s Github page. Also, you can check our company's page FlutterDevs.