Recreate ChangeNotifier Instance when page is closed - flutter

I'm creating a page that displays some data from a ChangeNotifier, when I close the page (by clicking on back button) and go forward again, the data from ChangeNotifier remains the same (used in a Text). Is a ChangeNotifier a singleton? Can I make a "factory" so a new instance will be created every time?
login page
#override
Widget build(BuildContext context) {
loginNotifier = context.watch<LoginNotifier>();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
scrolledUnderElevation: 0.0,
backgroundColor: Colors.transparent,
actions: [],
elevation: 0,
),
body: LayoutBuilder(builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText:
AppLocalizations.of(context)!.login_enter_email,
),
controller: emailController,
),
const SizedBox(
height: 24,
),
Text(loginNotifier?.currentCountry?.localizedName ?? "") // keeps the same value of previous time
]),
],
),
),
),
);
}),
);
}
App
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginNotifier()),
],
child: MaterialApp(
title: 'App',
theme: ThemeData(
primarySwatch: Colors.blue,
scaffoldBackgroundColor: Color(0xFFF7FCFC),
useMaterial3: true,
textTheme: kTextTheme),
home: const HomePage(),
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
),
);
}

A way around this is to create a function that resets the values inside your LoginNotifier class and calls the function when a user taps on a button in the page.
Or wrap the scaffold with a WillPopScope and call the function. WillPopScope tells you if a user is pressing the back button.

Related

How to add Navigation to Grid View - Flutter Layouts

I want to add Navigation to my GridListTiles but I don't know how. My GridView is like this :
Also if anyone knows the container navigation it will also work for GridListTile
Update1: Added more code
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout demo'),
),
body: Center(child: _buildGrid()),
),
);
}
Widget _buildGrid() => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30));
List<Widget> _buildGridTileList(int count) =>
List.generate(
count,
(i) => InkWell(
onTap: ()=> Navigator.of(context).push(
MaterialApp(
builder: (context) =>
YourWidgetName()//Added you can also pass the variable i to the next screen by passing it into your next screen constructor.
)
),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black)
),
child: Image.asset('images/pic$i.jpg'),
),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout demo'),
),
body: Center(child: _buildGrid(context)),
),
);
}
Widget _buildGrid(BuildContext context) => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30,context));
List<Widget> _buildGridTileList(int count,BuildContext context) =>
List.generate(
count,
(i) => InkWell(
onTap: ()=> Navigator.of(context).push(MaterialPageRoute(builder: (context)=>Scaffold(body:Text(i.toString())))),//you can pass the index (i) in the constructor of YourWidget(i) something like this if needed
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black)
),
child: Image.asset('images/pic$i.jpg'),
),
),
);
}

textformfield reloads screen when focused after navigation

...
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'haha',
theme: ThemeData(
fontFamily: 'Iransans',
primaryColor: Palette.primaryColor,
canvasColor: Palette.primaryColor, //my custom color
accentColor: Palette.accentColor),
initialRoute: "/",
routes: {
"/": (context) => Directionality(
textDirection: TextDirection.rtl, child: SplashPage()),
...
and in SplashPage, I navigate from here to login page
#override
Widget build(BuildContext context) {
Future.delayed(const Duration(seconds: 4), () {
Navigator.pushNamed(context,"/login" /*,arguments: "free"*/);
});
return const Scaffold(
backgroundColor: Palette.white,
body: Center(
child:
SizedBox(height: 60, width: 60, child: CircularProgressIndicator()),
),
);
}
here is my TextField in LoginPage. the page that doesn't work
but when navigating here from MainPage directly everything is well
Scaffold(
backgroundColor: Palette.primaryColor,
body: SingleChildScrollView(
child: Column(
children: <Widget>[
TextField(
autofocus: true,
textAlign: TextAlign.center,
//controller: _textFieldControllerUserName,
keyboardType: TextInputType.name,
),
],
),
),
);
flutter keeps pages in a stack and the whole thing in the stack is working :(
build method runs even when the page is in backStack,
so I moved my navigator to initState()
class SplashPageState extends State<SplashPage> {
#override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 4), () {
_navigateLoginPage(context);
});
}
#override
Widget build(BuildContext context) {
return const Scaffold(
backgroundColor: Palette.white,
body: Center(
child:
SizedBox(height: 60, width: 60, child: CircularProgressIndicator()),
),
);
}

ListTile title makes it's children take full width no matter what

I need to insert a TextField in ListTile's title and make it take only the width of the text inside. It works perfectly outside of a ListTile, but inside a title it takes full width of a ListTile no matter what. What can I do?
DartPad: https://dartpad.dev/87f397c71f3e449e866cea9914d13bb0
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IntrinsicWidth(
child: TextField(
decoration: InputDecoration(
hintText: 'Title',
),
),
),
Container(
width: 300,
child: Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onLongPress: () {
print('Longpress fired');
},
child: ListTile(
title: IntrinsicWidth(
child: TextField(
decoration: InputDecoration(
isDense: true,
hintText: 'Title',
contentPadding: EdgeInsets.all(0),
),
),
),
subtitle: Text('subtitle'),
),
),
),
),
],
),
),
);
}
}
UPDATE:
I managed to make the TextField take needed space with this code, but now I am sufferng from overflow:
ListTile(
title: Expanded(
child: Row(
children: [
IntrinsicWidth(
child: TextField(),
),
],
),
),
),
I suggest you use a SizedBox for the trailing property in ListView.
It's probably better to just create your own custom widget for this, it's actually not that hard. Looks something like:
Column(
children: [
Column(
children: [
SizedBox(
width: use an approriate MediaQuery here,
child: TextField(),
),
Text('subtitle')
],
),
Maybe even throw in a padding in the Container.

Flutter: How to implement floating SearchBar (Custom AppBar) with Drawer like Google Apps

Can somebody please tell me how can I integrate the menu drawer inside the Row widget instead of in a Scaffold widget? Something like Gmail's app (search with drawer icon).
It's very simple.
Screenshot of the output
Steps:
Step 1:
Define the scaffold with a custom Appbar widget
return Scaffold(
appBar: FloatAppBar(),
body: Center(
child: Text('Body'),
),
drawer: Drawer(
child: SafeArea(
right: false,
child: Center(
child: Text('Drawer content'),
),
),
),
);
Step 2:
Implement the PreferredSizeWidget to create a custom AppBar
class FloatAppBar extends StatelessWidget with PreferredSizeWidget {
step 3:
Use Scaffold.of(context).openDrawer(); to open the drawer when required.
Here is the complete snippet.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: FloatAppBar(),
body: Center(
child: Text('Body'),
),
drawer: Drawer(
child: SafeArea(
right: false,
child: Center(
child: Text('Drawer content'),
),
),
),
);
}
}
class FloatAppBar extends StatelessWidget with PreferredSizeWidget {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: 10,
right: 15,
left: 15,
child: Container(
color: Colors.white,
child: Row(
children: <Widget>[
Material(
type: MaterialType.transparency,
child: IconButton(
splashColor: Colors.grey,
icon: Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
),
Expanded(
child: TextField(
cursorColor: Colors.black,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 15),
hintText: "Search..."),
),
),
],
),
),
),
],
);
}
#override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
See the live demo here.
the AppBar widget alredy has mechanisms for that,
AppBar(
drawaer: YourDrawer(),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: (){}
)
]
);
it will create the Gmail appbar you want

Scaffold Drawer to showModalBottomSheet

At the homescreen of myApp() I have a stateless widget, it contains a MaterialApp and a Scaffold. Scaffold have a property of drawer and I passed I created a drawer, and one of the item in my drawer needs to open the showModalBottomSheet while closing the drawer. How can I achieve this? I've tried passing the context itself, and as globalKey.currentContext (after GlobalKey<ScaffoldState> globalKey = GlobalKey();) but the drawer sometimes closes, other time gives me a NoMethodFoundException (or something like that)
In short, how to have a Scaffold drawer that have one of the item, when tapped closes the drawer and showModalBottomSheet?
Current code:
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
GlobalKey<ScaffoldState> homeScaffoldKey = GlobalKey();
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: Scaffold(
key: homeScaffoldKey,
drawer: showDrawer(homeScaffoldKey.currentContext),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
),
],
),
),
);
}
}
Drawer showDrawer(BuildContext context) {
void showCalendarsModalBottom() {
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return ListView.builder(
itemCount: repo.calendars.length,
itemBuilder: (builder, index) {
return StatefulBuilder(
builder: (builder, StateSetter setState) => ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Checkbox(
value: repo.getIsEnabledCal(repo.getCal(index)),
onChanged: (value) {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
Container(
height: 14,
width: 14,
margin: EdgeInsets.only(left: 2, right: 6),
decoration: BoxDecoration(
color: Colors.redAccent,
shape: BoxShape.circle,
),
),
Text(
repo.getCal(index).name,
style: TextStyle(
fontSize: 16,
),
),
],
),
onTap: () {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
);
},
);
},
);
}
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.pop(context);
showCalendarsModalBottom();
},
)
],
),
);
}
Updated working code based on your code snippet:
You'll need to have statefulwidget that will help to pass the context from drawer to bottomsheet and pass the context as an argument in showCalendarModalBottomSheet() method.
void main() {
runApp(new MaterialApp(home: Timeline(), debugShowCheckedModeBanner: false));
}
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: MyHomePage()
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: AppDrawer(),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
//HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
)
],
)
);
}
Widget AppDrawer() {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.of(context).pop();
showCalendarsModalBottom(context);
},
)
],
),
);
}
Future<Null> showCalendarsModalBottom(context) {
return showModalBottomSheet(context: context, builder: (context) => Container(
color: Colors.red,
// your code here
));
}
}
And the output is: When app drawer menu Calendar is tapped, it closes and opens the bottomsheet seamlessly. If you tap on app drawer again and repeat steps, you see smooth transition between drawer and bottomsheet. Hope this answers your question.