navigating to new page after dismissing a widget throws exception - dart

I have a ListView of items in my app and each item of that list is wrapped in a Dismissible widget so as to enable swipe to delete functionality. I also have a FloatingActionButton and on pressing that I navigate to a new page. All the functionality involving swipe to dismiss works fine but if I press the floating action button after dismissing an item, an exception is thrown with the following message:
I/flutter (23062): A dismissed Dismissible widget is still part of the tree.
I/flutter (23062): Make sure to implement the onDismissed handler and to immediately remove the Dismissible
I/flutter (23062): widget from the application once that handler has fired.
Here's the code:
Widget build(BuildContext context) {
final String testVal = widget.testStr;
return Scaffold(
appBar: AppBar(
title: Text('My Device Info'),
),
body: FutureBuilder<AndroidDeviceInfo>(
future: _deviceInfoPlugin.androidInfo,
builder: (BuildContext context, AsyncSnapshot<AndroidDeviceInfo> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
final androidDeviceInfo = snapshot.data;
return ListView(
children: <Widget>[
Dismissible(
key: Key('1'),
background: Container(color: Colors.red,),
onDismissed: (direction){
},
child: ListTile(
leading: Icon(Icons.info),
title: Text('Android build version: ${androidDeviceInfo.version.release}',
style: TextStyle(fontSize: 18.0),),
),
),
ListTile(
leading: Icon(Icons.info),
title: Text('Android device: ${androidDeviceInfo.device}',
style: TextStyle(fontSize: 18.0),),
),
ListTile(
leading: Icon(Icons.info),
title: Text('Android device hardware: ${androidDeviceInfo.hardware}', style: TextStyle(fontSize: 18.0),),
)
],
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context){ return new AboutPage();}));
},
child: Icon(Icons.add),
),
);
}
How can I avoid this exception from being thrown? Am I doing something wrong here?

The way you initialize the Key to your Dismissable is wrong because Flutter thinks there can be more Dismissable widgets with the same key value.
Add uuid: ^1.0.3 to your dependencies,
Dismissible(
key: Key(Uuid().v4().toString()), //use the uuid.
.
.
.

Related

OpenFlutter/flutter_oktoast shows on main page only?

I am using this library to show custom toast in my app. I have multiple pages in my app. The problem is, toast appears on the main page even when I call showToastWidget(...) from any other pages.
Main Page
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: SizedBox(
height: 50,
width: 50,
child: Image(image: AssetImage('assets/ic_logo.png')),
),
),
),
);
}
Page 2
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Reset Password'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToastWidget(
Text('Hello. I am Toast!!!'),
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
),
);
}
When I call showToastWidget(...) from this page, it appears on Main Page
EDIT 1
I get this exception when I pass context to showToastWidget()
I/flutter (24327): The following NoSuchMethodError was thrown while handling a gesture:
I/flutter (24327): The getter 'position' was called on null.
I/flutter (24327): Receiver: null
I/flutter (24327): Tried calling: position
I/flutter (24327):
I/flutter (24327): When the exception was thrown, this was the stack:
I/flutter (24327): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
I/flutter (24327): #1 showToastWidget (package:oktoast/src/toast.dart:210:40)
Looks like the OKToast library does not support multiple OKToast widgets in the same app. You will have to wrap your entire app in an OKToast widget, full sample:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return OKToast(
child: MaterialApp(
home: MainPage(),
),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FlatButton(
child: Text("Show"),
onPressed: () {
showToast(
"Main Page toast",
duration: Duration(seconds: 2),
);
},
),
SizedBox(height: 12.0),
FlatButton(
child: Text("Go to next page"),
onPressed: () => _goToNextPage(context),
),
],
),
),
);
}
void _goToNextPage(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Second Page"),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToast(
"Second Page toast",
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
);
}
}
I am the author of OKToast, I am very glad that you use this library.
This tip of Toast is based on the design of toast in android on the mobile side.
The inspiration for OKToast itself comes from toast, so it is inevitable that it has received an impact, not adapting to the individual page-level Toast.
If you need a page-level OKToast component, you might need to do this:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
import 'package:simple_widget/simple_widget.dart';
class CategoryPage extends StatefulWidget {
#override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
#override
Widget build(BuildContext context) {
return OKToast(
child: SimpleScaffold(
title: "CategoryPage",
actions: <Widget>[
Builder(
builder: (ctx) => IconButton(
icon: Icon(Icons.trip_origin),
onPressed: () {
showToast("page toast", context: ctx); // use context to show toast in the page-level OKToast.
},
),
),
],
child: Container(),
),
);
}
}

In CupertinoNavigationBar how do i show a button besides back button in leading?

I tried something like this:
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(widget.title),
),
child: Center(
child: Container(
child: CupertinoButton.filled(
child: const Text('Push screen'),
onPressed: () {
CupertinoNavigationBar navBar = CupertinoNavigationBar(
leading: Row(children: <Widget>[
const CupertinoNavigationBarBackButton(),
CupertinoButton(
child: const Text('Button 2'),
padding: EdgeInsets.zero,
onPressed: () {},
),
]),
);
Navigator.push(context, CupertinoPageRoute<CupertinoPageScaffold>(
builder: (_) => CupertinoPageScaffold(
navigationBar: navBar,
child: Center(child: const Text('Content')),
)
));
},
),
),
),
);
}
And when tapping the button, it fails with
I/flutter (30855): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (30855): The following NoSuchMethodError was thrown building CupertinoNavigationBarBackButton(dirty):
I/flutter (30855): The getter 'canPop' was called on null.
I/flutter (30855): Receiver: null
I/flutter (30855): Tried calling: canPop
The reason is that this code in CupertinoNavigationBarBackButton returns null
final ModalRoute<dynamic> currentRoute = ModalRoute.of(context);
I wonder why that's the case? Is it because when I push the button, context still hasn't gotten the route yet?
What's the correct way to manually add a back button?
Thanks in advance.
Here is a very simple snippet of code. I hope it helps you:
class DemoView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: GestureDetector(
onTap: () {
debugPrint('Back button tapped');
},
child: Row(
children: <Widget>[
Icon(CupertinoIcons.left_chevron),
Text(
'Back',
style: TextStyle(
color: CupertinoColors.activeBlue,
),
),
],
),
),
middle: Text('Demo'),
trailing: GestureDetector(
onTap: () {
debugPrint('add icon tapped');
},
child: Icon(
CupertinoIcons.add,
color: CupertinoColors.black,
),
),
),
child: Center(
child: Text(
'Content',
style: TextStyle(fontSize: 36.0),
),
),
);
}
}
Which is the correct way to add manually back button?
I use code like this:
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(widget.title),
leading: CupertinoNavigationBarBackButton(
onPressed: () => Navigator.of(context).pop(),
),
child: _myAppBody() // return widget
),
);
}

Flutter - Calling persistent bottom sheet from another class

I have a persistent bottom sheet in Flutter which currently exists inside an icons onPressed(){} property.
I would like to move this persistent bottom sheet to a new class on its own but I can't seem to get it working. I am still new to flutter and can't work out how to build the structure for the persistent bottom bar.
I have currently tried the below but when I run my code, I get an exception that is thrown.
main.dart
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Test App'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.open_in_new),
onPressed: (){
ShowBottomSheet(context);
},
)
],
),
bottom_modal_sheet.dart
final GlobalKey<ScaffoldState> _scaffoldKey = new
GlobalKey<ScaffoldState>();
void ShowBottomSheet(BuildContext context) {
_scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context)
{
return new Container(
width: double.infinity,
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 24.0, top: 16.0),
child: Text('Site Location',
style: TextStyle(fontSize: 24.0, color: Color(0xFF1181A1), fontWeight: FontWeight.bold,),
),
),
Padding(
padding: EdgeInsets.only(left: 24.0, top: 16.0),
child: Text('11 Carr Road, Three Kings, Auckland 1042',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 16.0, color: Color(0xFF8596AC)),
),
),
Padding(
padding: EdgeInsets.only(left: 24.0, top: 24.0, right: 24.0, bottom: 16.0),
child: RasiedGradientButton(
child: Text('Set Location', style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold, color: Colors.white)),
gradient: LinearGradient(
colors: <Color>[Color(0xFFFCCF58), Color(0xFFFEAA00)]
),
onPressed: (){
print('button clicked');
},
)
),
],
));
});
}
I am getting the error:
I/flutter (19024): ══╡ EXCEPTION CAUGHT BY GESTURE
╞═══════════════════════════════════════════════════════════════════
I/flutter (19024): The following NoSuchMethodError was thrown while
handling a gesture:
I/flutter (19024): The method 'showBottomSheet' was called on null.
I/flutter (19024): Receiver: null
I/flutter (19024): Tried calling: showBottomSheet<Null>(Closure:
(BuildContext) => Container)
I/flutter (19024):
You just need call showBottomSheet in your screen widget that you want show the bottom sheet and return the widget of your custom bottom sheet. The snippet show how to do this. Read the source comments.
// our Screen widget class
class MyScreen extends StatefulWidget {
#override
_MyScreenScreenState createState() => _MyScreenScreenState();
}
class _MyScreenState extends State<MyScreenScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test App'),
actions: <Widget>[
IconButton( icon: Icon(Icons.open_in_new),
onPressed: (){
_showMyBottomSheet();
},
)
],
),
);
}
void _showMyBottomSheet(){
// the context of the bottomSheet will be this widget
//the context here is where you want to showthe bottom sheet
showBottomSheet(context: context,
builder: (BuildContext context){
return MyBottomSheetLayout(); // returns your BottomSheet widget
}
);
}
}
//your bottom sheet widget class
//you can put your things here, like buttons, callbacks and layout
class MyBottomSheetLayout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(); // return your bottomSheetLayout
}
}

TextField's autofocus property causing the widget into 'dirty' State

AppBar has a search icon, when clicked it displays a TextField in the 'title' property. When search icon is clicked I need TextField to autofocus. Problem is with 'autofocus' property, if set to true, instead of only changing the state of the title property, something is causing the widget to turn into a 'dirty' state. This causes the main build function to get called which rebuilds the entire thing.
Tried to replicate this and provide a sample app but strangely enough it seems to work just fine in the demo.
Any debug suggestions?
AppBar(
centerTitle: true,
title: StreamBuilder(
stream: false,
initialData: symbolBloc.isSearching,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot){
if(snapshot.data) {
return TextField(
// autofocus: true, <--- here
controller: searchQuery,
style: new TextStyle(
color: Colors.white,
),
);
}
return new Text("", style: new TextStyle(color: Colors.white)
);
}),
backgroundColor: Colors.blueGrey,
elevation: 0.0,
actions: <Widget>[
StreamBuilder(
stream: bloc.isSearchActive,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot){
if(snapshot.data)
return activeSearchIconButton(symbolBloc);
return searchIconButton(symbolBloc);
},
),
],
),
searchIconButton(SymbolBloc bloc){
return new IconButton(
icon: new Icon(
Icons.search,
color: Colors.white,
),
tooltip: 'Search',
onPressed: (){
bloc.displaySearchField(true);
},
);
}
activeSearchIconButton(SymbolBloc bloc){
return new IconButton(
icon: new Icon(
Icons.close,
color: Colors.white,
),
tooltip: 'Search',
onPressed: (){
bloc.displaySearchField(false);
},
);
}
You can set FocusNode property of TextField and on Button click call FocusScope.of(context).requestFocus(_focusNode);
The problem had do with InheritedWidgets. Having more than one InheritedWidget was the cause. I had a parent widget wrapping the runApp() function (holds a reference to api object) and a child widget wrapping each route (holds reference to bloc class for each specific screen).
Problem went away once I moved the contents of child widget over to the parent and removed the child InheritedWidget altogether.

How to display iOS/cupertino alert dialog in Android using Flutter?

I was trying to display a iOS themed dialog box in my Flutter app, but I was unable to find anything in the docs
The keyword for Android theme/style is Material (default design), the keyword for iOS theme/style is Cupertino. Every iOS theme widget has the prefix Cupertino. So that, for you requirement, we can guess the keyword is CupertinoDialog/CupertinoAlertDialog
You can refer here for all of them https://flutter.io/docs/reference/widgets/cupertino
new CupertinoAlertDialog(
title: new Text("Dialog Title"),
content: new Text("This is my content"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text("Yes"),
),
CupertinoDialogAction(
child: Text("No"),
)
],
)
First you check if platForm ios or android .. then return widget for the current device ..
Future<bool> showAlertDialog({
#required BuildContext context,
#required String title,
#required String content,
String cancelActionText,
#required String defaultActionText,
}) async {
if (!Platform.isIOS) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
if (cancelActionText != null)
FlatButton(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
FlatButton(
child: Text(defaultActionText),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
}
// todo : showDialog for ios
return showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
if (cancelActionText != null)
CupertinoDialogAction(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
child: Text(defaultActionText),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
}
I used CupertinoAlertDialog inside the ShowDialog, you can find the same below
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: new Text("Dialog Title"),
content: new Text("This is my content"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text(StringConstants.BIOMETRICAUTHORIZED),
),
CupertinoDialogAction(
child: Text("No"),
)
],
)
);

Resources