Flutter Android back button navigating back to main page - android

I would like to be able to use the back button to navigate back to the main page instead of closing the app, I have seen the WillPopScope widget option but that needs to show a dialog, is there a way to pop back using the android back button without a dialog?

You can Use Navigator.canPop() Method to avoid app from Exiting.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return Navigator.canPop(context);
},
child: Scaffold(
appBar: AppBar(title: const Text('HomePage')),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
backgroundColor: Colors.red,
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SecondPage()));
},
),
),
);
}
}

The onWillPop callback needs a Future of bool returned. You just can do
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
return Future.value(true); // or return Future.value(false);
},
child: Container()
);
}

Related

Error trying to read items from a list of strings to show in a ListView

In the code below I am trying to build a basic ToDo list app using flutter. I have a FAB and when it is pressed, it asks the user to enter a text in the popped up alert dialog that contains a TextField. I also use a TextEditingController to get the text and add it to a list of strings.
I have a counter variable to keep track of items being added to the list and to use it as index of the list when I want to show the item from the list and add it to the ListView as a ListTile.
When I run the code it says the index is out of range and I really don't know what else should I take care of. Sorry if my question is basic, I am newbie.
My Code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ToDo List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyTaskList(),
);
}
}
class MyTaskList extends StatefulWidget {
#override
_MyTaskListState createState() => _MyTaskListState();
}
class _MyTaskListState extends State<MyTaskList> {
final _taskItems = <String>[];
var _counter = 0;
final myController = TextEditingController();
#override
void dispose(){
myController.dispose();
super.dispose();
}
void _addTask(String task){
setState(() {
_taskItems.add(task);
});
myController.clear();
}
Widget _buildTaskList() {
return new ListView.builder(
itemCount: _taskItems.length,
itemBuilder: (BuildContext context, _) {
print(_taskItems);
return _buildTask(_taskItems[_counter]);
}
);
}
Widget _buildTask(String task) {
return new ListTile(
title: new Text(task),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ToDo List"),
),
floatingActionButton: FloatingActionButton(
onPressed: () => showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: Text("New Task"),
content: TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter New Task",
),
),
actions: <Widget>[
TextButton(
onPressed: () => {
_addTask(myController.text),
Navigator.pop(context, "ok"),
_counter++,
print(_counter),
print(_taskItems),
},
child: const Text("OK")),
],
)
),
child: Center(child:Icon(Icons.add)),
),
body: _buildTaskList(),
);
}
}
Edit as below. You can use the ListViewBuilder index, why do you use counter? I think, your initial counter value is 0 but the list is empty. You try to get element 0 (or first)` of empty list, but there is no element.
Widget _buildTaskList() {
return new ListView.builder(
itemCount: _taskItems.length,
itemBuilder: (BuildContext context, index) {
print(_taskItems);
return _buildTask(_taskItems[index]);
}
);
}

Flutter: widget has been unmounted issue during dismissible actions

i have an issue with the widget unmounted with dismissdirection action on flutter. When I left swipe the dismissible item with the deleted action confirmed, the error occured as following:
The following assertion was thrown while notifying status listeners for AnimationController:
This widget has been unmounted, so the State no longer has a context (and should be considered
defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if
the State is still active.
The full error codes are here
My code:
home_page.dart. The homepage I use statefulwidget and redirect to ExpensesCategoryHistory() Screen.
class HomePage extends StatefulWidget {
const HomePage({Key key, this.database, this.budget}) : super(key: key);
final DatabaseService database;
final Budget budget;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
PersistentTabController _controller;
_controller = PersistentTabController(initialIndex: 0);
return PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
backgroundColor: Colors.white,
handleAndroidBackButtonPress: true,
resizeToAvoidBottomInset: true,
stateManagement: true,
hideNavigationBarWhenKeyboardShows: true, // Recommended to set 'resizeToAvoidBottomInset' as true while using this argument. Default is true.
decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(10.0),
colorBehindNavBar: Colors.white,
),
popAllScreensOnTapOfSelectedTab: true,
popActionScreens: PopActionScreensType.all,
itemAnimationProperties: ItemAnimationProperties( // Navigation Bar's items animation properties.
duration: Duration(milliseconds: 200),
curve: Curves.ease,
),
screenTransitionAnimation: ScreenTransitionAnimation( // Screen transition animation on change of selected tab.
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
navBarStyle: NavBarStyle.style15, // Choose the nav bar style with this property.
);
}
}
List<Widget> _buildScreens() {
return [
Home(),
ExpensesCategoryHistory(),
BudgetPage(),
Container(),
Container()
];
}
Then, in the ExpensesCategoryHistory() class. So, user can select the category and prompt expenses based on the category (when the list item is tapped.) Refer Steps 4 of error occured with images
class ExpensesCategoryHistory extends StatefulWidget {
#override
_ExpensesCategoryHistoryState createState() => _ExpensesCategoryHistoryState();
}
class _ExpensesCategoryHistoryState extends State<ExpensesCategoryHistory> {
var categoryList = ["Beauty", "Entertainment", "Food & Drinks", "Groceries", "Medical", "Transport", "Others"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: kPrimaryColor,
title: Text('Expenses History By Category'),
),
body: _buildContents(context),
floatingActionButton: FloatingActionButton(
backgroundColor: kPrimaryColor,
child: Icon(Icons.add),
onPressed: () => EditExpensesPage.show(context,
database: Provider.of<DatabaseService>(context, listen: false),
),
),
);
}
Widget _buildContents(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
return ListView.builder(
itemCount: categoryList.length,
itemBuilder: (context, index){
return ListTile(
title: Text('${categoryList[index]}'),
onTap: () {
if(mounted){
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (context) => ExpensesHistory(Category: categoryList[index]),
),
);
}
},
);
},
);
}
}
Then, lasty the main issue is here. When user select on delete action after left swipe of item. The error code occurred and did not perform Navigator.of(context).pop(true)
ExpensesHistory class
class ExpensesHistory extends StatelessWidget {
// Declare a field that holds the Todo.
final String Category;
// In the constructor, require a Todo.
ExpensesHistory({Key key, #required this.Category}) : super(key: key);
Future<void> _delete(BuildContext context, Expense expense) async {
try {
final database = Provider.of<DatabaseService>(context, listen: false);
await database.deleteExpenses(expense);
} on PlatformException catch (e) { //handle error
PlatformExceptionAlertDialog(
title: 'Operation failed',
exception: e,
).show(context);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: kPrimaryColor,
title: Text('$Category'),
),
body: _buildContents(context),
floatingActionButton: FloatingActionButton(
backgroundColor: kPrimaryColor,
child: Icon(Icons.add),
onPressed: () => EditExpensesPage.show(context,
database: Provider.of<DatabaseService>(context, listen: false),
),
),
);
}
Widget _buildContents(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
List myExpenses = [];
return StreamBuilder<List<Expense>>(
stream: database.expensesStream(),
builder: (context, snapshot) {
return ListItemsBuilder<Expense>(
snapshot: snapshot,
itemBuilder: (context, expense) =>
Dismissible(
confirmDismiss: (DismissDirection direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Delete Confirmation"),
content: const Text(
"Are you sure you want to delete this item?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Delete"),
),
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
],
);
},
);
},
key: UniqueKey(),
background: slideRightBackground(),
direction: DismissDirection.endToStart,
onDismissed: (direction) => _delete(context, expense),
child: ExpensesListTile(
expense: expense,
onTap: () => ExpensesCategoryPage.show(context, expense),
category: Category,
),
),
);
},
);
}
}
Steps of error occured with images
To ExpensesCategoryHistory class, render page here
Select category Beauty here
Render Beauty category item here
Confirm Delete action on category item after left swipe of dismissible here
Selected delete, prompt error code + item not deleted
Return stay step 3
Sorry for the full codes, but I wonder if my implementation issue on flutter/dart? I have been solving for days but still same issue, even I've read couple related issues on stackoverflow. Any help is greatly appreciated !
Tested
All widgets are mounted within 3 classes after routing.
Error occurred when deleted action is tapped but widget is mounted still.

How do I make the build function wait until a button is pressed in an alert in init?

I am trying to display an Alert that shows a disclaimer to the user as soon as the app is opened. The build method will run, that is the app will start its processing only after the user presses okay on the alert.
I've managed to show the alert in init using
SchedulerBinding.instance.addPostFrameCallback((_) => AlertWindow().showAlert(context));
or
Future.delayed(Duration.zero, () => AlertWindows().showAlert(context));
This shows the alert, but the app starts building in the background. I want the app to run/build only after OKAY button is pressed, and after the alert is popped.
Hey I implemented some code, you can try this code directly on dartPad Paste the code in this Editor
I used setState, if it is for real time project you can use Providers or bloc, for performance.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Widget viewHolder;
void initState() {
viewHolder = Container();
WidgetsBinding.instance
.addPostFrameCallback((_) => afterPostFrameCallBack());
super.initState();
}
afterPostFrameCallBack() {
_showDialog();
}
#override
Widget build(BuildContext context) {
return viewHolder;
}
Widget _buildView() {
return Container(child: Text('This is after okay button'));
}
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: Text("App Update Available"),
content: Text(
"We have fixed some issues and added some cool features in this update"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
FlatButton(
child: new Text("ok"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
viewHolder = _buildView();
});
},
),
],
);
},
);
}
}

Flutter: close app on back press is not working

In the app, on starting splash screen appears after that I reach to the login screen where there is no app bar or back button but I'm able to back from device back button. I don't want to back on the splash screen when the back is pressed from login screen. I tried many solutions but they are not working.
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xyz',
home: LoginPage(),
debugShowCheckedModeBanner: false,
);
}
}
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (Navigator.canPop(context)) {
Navigator.pop(context); // i tried both the way
Navigator.of(context).pop();
} else {
SystemNavigator.pop();
}
},
child:Scaffold(
body: Container(.........................//here is my desiging stuff
I also tried return Future.value(false); with true and false value –
Splash screen code
#override
void initState (){
super.initState();
// TODO initial state stuff
new Future.delayed(
const Duration(seconds: 2),
() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
));
}
Simply pass empty function to - WillPopScope widget.
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {}, // Empty Function.
child: Scaffold(
body: Container(), //here is my desiging stuff
));
}
}
OR
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: Scaffold(
body: Container(),
appBar: AppBar(),
),
);
}
If you never want to goto Splash Screen.
It's Better to use:
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => Login()));
Just change your code to:
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async => false,
child:Scaffold(
body: Container(.........................//here is my desiging stuff
I had the same problem, and it was because wrongly surrounding MaterialApp with WillPopScope (instead of surrounding Scaffold).
Appreciating #anmol-majhail, here is the correct solution in my case:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
home: WillPopScope(
child: Scaffold(...),
onWillPop: () async {
return false;
}),
);
}

initState called multiple times

In my app I have a login screen and a home screen. When navigating from the login to the home screen I read data from a .txt file and show 4 random data points. I am getting the data from the file in my initState so that it isn't called multiple times when the state changes and then waiting on it with a future builder like...
class _HomeScreenState extends State<HomeScreen> {
Future<bool> _future;
#override
initState() {
super.initState();
print('in initState about to call _getData');
_future = _getData();
}
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
case ConnectionState.done:
if (snapshot.hasError) {
return new Center(
child: Text('Error'),
);
} else {
return new ListView(
children: <Widget>[
//my view
],
);
}
}
}
);
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
return Navigator.pop(context);
},
child: Scaffold(
body: futureBuilder,
),
)
);
Now when I navigate back to the login screen using the back button and then go back to the home screen initState will go off multiple times (this can be seen by the print statement I left in). As you go back and forth between these two screens (pop homescreen, push homescreen) initState will be called exponentially more times. I am so confused, any help is appreciated!
EDIT: Full code for both login and home screen can be found https://github.com/ViscousOx/Flutter-Stuff
Try that: It work
Use the default statement with the ConnectionState.waiting.
class _HomeScreenState extends State<HomeScreen> {
Future<bool> _future;
#override
initState() {
super.initState();
print('in initState about to call _getData');
_future = _getData();
}
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
default:
if (snapshot.hasError) {
return new Center(
child: Text('Error'),
);
} else {
return new ListView(
children: <Widget>[
//my view
],
);
}
}
}
);
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
return Navigator.pop(context);
},
child: Scaffold(
body: futureBuilder,
),
)
);
Connection waiting state can be avoided. I used as:
FutureBuilder(future: _isUserLoggedIn(),
builder: (ctx, loginSnapshot) =>
loginSnapshot.data == true ? AppLandingScreen() : SignUpScreen()
),
As you go back and forth between these two screens (pop homescreen, push homescreen)
It sounds like you're creating your HomeScreen each time you enter the screen, so it'll be a brand new widget, and thus normal that initState() gets called.
If you want to keep the same screen (object) around for the lifetime of your app, there are a number of solutions such as Stack and Offstage so that your widget lives on, but isn't visible.

Categories

Resources