I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?
Related
I have a splash screen in my homepage activity which should then redirect to my second activity:
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
Timer(const Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
SecondScreen()
)
)
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child:FlutterLogo(size:MediaQuery.of(context).size.height)
);
}
}
class SecondScreen extends StatelessWidget { //checking if internet connection exists here
late StreamSubscription subscription;
var isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState(){
getConnectivity();
super.initState(); //initState() is undefined
}
getConnectivity() =>
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true); //setState() is undefined
}
},
);
#override
void dispose() {
subscription.cancel();
super.dispose(); //dispose() is undefined
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment:MainAxisAlignment.center,
children:[
Image(
image: const AssetImage('images/logo.png'),
height: AppBar().preferredSize.height,),
const SizedBox(
width: 15,
),
Text(
widget.title
),
]
)
)
);
}
showDialogBox() => showCupertinoDialog<String>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('No internet connection'),
content: const Text('Please make sure you have an active internet connection to continue'),
actions: <Widget>[
TextButton(
onPressed: () async {
Navigator.pop(context, 'Cancel');
setState(() => isAlertSet = false);
isDeviceConnected =
await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true);
}
},
child: const Text('OK'),
),
],
),
);
}
The flow is such that, in the homepage activity a splash screen will open and then it will redirect to the second activity which will check if the user has an active internet connection.
I tried changing the SecondScreen to statefulWidget, but I still keep getting the same error.
Stateless: A stateless widget is like a constant. It is immutable. If you want to change what is displayed by a stateless widget, you'll have to create a new one.
Stateful: Stateful widgets are the opposite. They are alive and can interact with the user. Stateful widgets have access to a method named setState, which basically says to the framework "Hello, I want to display something else. Can you redraw me please ?".
A stateless widget can only be drawn once when the Widget is loaded/built and cannot be redrawn based on any events or user actions.
This kind of widget has no state, so they can’t change according to an internal state, they only react to higher widget changes.
more information read this documentation StatefulWidget and StatelessWidget
convert in stateful widget
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
there is no initState in a stateless widget but you can call a function after rebuild of a stateless widget using this:
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
// do something
print("Build Completed");
});
return Container(
color: Colors.blue,
child: WhatEverWidget()
);
}
}
I'm trying to create a custom menu bar in my app. Right now, the biggest issue I'm having is passing a state for when it's expanded to it's children after a setState occurs.
I thought about inheritance, but from what I've tried all inheritance needs to be in-line. I can't create a widget where the children [] are fed into the constructor on an ad-hoc basis.
My current approach is to use a GlobalKey to update the State of the children widgets being inserted into the StateFul while updating them directly.
The children for my MenuBar are declared as:
List<MenuBarItem> menuItems;
MenuBarItem is an abstract interface class that I intend to use to limit the widgets that can be fed in as menuItems to my MenuBar.
abstract class iMenuItem extends Widget{}
class MenuBarItem extends StatefulWidget implements iMenuItem{
At some iterations of this script, I had a bool isExpanded as part of the iMenuItem, but determined it not necessary.
Here is my code at its current iteration:
My Main:
void main() {
// runApp(MainApp());
//runApp(InherApp());
runApp(MenuBarApp());
}
class MenuBarApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: MenuBar(
menuItems: [
// This one does NOT work and is where I'm trying to get the
// value to update after a setState
MenuBarItem(
myText: 'Outsider',
),
],
),
),
);
}
}
My Code:
import 'package:flutter/material.dart';
/// Primary widget to be used in the main()
class MenuBar extends StatefulWidget{
List<MenuBarItem> menuItems;
MenuBar({
required this.menuItems,
});
#override
State<MenuBar> createState() => MenuBarState();
}
class MenuBarState extends State<MenuBar>{
bool isExpanded = false;
late GlobalKey<MenuBarContainerState> menuBarContainerStateKey;
#override
void initState() {
super.initState();
menuBarContainerStateKey = GlobalKey();
}
#override
Widget build(BuildContext context){
return MenuBarContainer(
menuItems: widget.menuItems,
);
}
}
class MenuBarContainer extends StatefulWidget{
List<MenuBarItem> menuItems;
late Key key;
MenuBarContainer({
required this.menuItems,
key,
}):super(key: key);
#override
MenuBarContainerState createState() => MenuBarContainerState();
}
class MenuBarContainerState extends State<MenuBarContainer>{
bool isExpanded = false;
#override
void initState() {
super.initState();
isExpanded = false;
}
#override
Widget build(BuildContext context){
List<Widget> myChildren = [
ElevatedButton(
onPressed: (){
setState((){
this.isExpanded = !this.isExpanded;
});
},
child: Text('Push Me'),
),
// This one works. No surprise since it's in-line
MenuBarItem(isExpanded: this.isExpanded, myText: 'Built In'),
];
myChildren.addAll(widget.menuItems);
return Container(
child: Column(
children: myChildren,
),
);
}
}
/// The item that will appear as a child of MenuBar
/// Uses the iMenuItem to limit the children to those sharing
/// the iMenuItem abstract/interface
class MenuBarItem extends StatefulWidget implements iMenuItem{
bool isExpanded;
String myText;
MenuBarItem({
key,
this.isExpanded = false,
required this.myText,
}):super(key: key);
#override
State<MenuBarItem> createState() => MenuBarItemState();
}
class MenuBarItemState extends State<MenuBarItem>{
#override
Widget build(BuildContext context){
GlobalKey<MenuBarState> _menuBarState;
return Row(
children: <Widget> [
Text('Current Status:\t${widget.isExpanded}'),
Text('MenuBarState GlobalKey:\t${GlobalKey<MenuBarState>().currentState?.isExpanded ?? false}'),
Text(widget.myText),
],
);
}
}
/// To give a shared class to any children that might be used by MenuBar
abstract class iMenuItem extends Widget{
}
I've spent 3 days on this, so any help would be appreciated.
Thanks!!
I suggest using ChangeNotifier, ChangeNotifierProvider, Consumer and context.read to manage state. You have to add this package and this import: import 'package:provider/provider.dart';. The steps:
Set up a ChangeNotifier holding isExpanded value, with a setter that notifies listeners:
class MyNotifier with ChangeNotifier {
bool _isExpanded = false;
bool get isExpanded => _isExpanded;
set isExpanded(bool isExpanded) {
_isExpanded = isExpanded;
notifyListeners();
}
}
Insert the above as a ChangeNotifierProvider in your widget tree at MenuBar:
class MenuBarState extends State<MenuBar> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyNotifier(),
child: MenuBarContainer(
menuItems: widget.menuItems,
));
}
}
After this you can easily read and write the isExpanded value from anywhere in your widget tree under the ChangeNotifierProvider, for example:
ElevatedButton(
onPressed: () {
setState(() {
final myNotifier = context.read<MyNotifier>();
myNotifier.isExpanded = !myNotifier.isExpanded;
});
},
child: Text('Push Me'),
),
And if you want to use this state to automatically build something when isExpanded is changed, use Consumer, which will be notified automatically upon every change, for example:
class MenuBarItemState extends State<MenuBarItem> {
#override
Widget build(BuildContext context) {
return Consumer<MyNotifier>(builder: (context, myNotifier, child) {
return Row(
children: <Widget>[
Text('Current Status:\t${myNotifier.isExpanded}'),
Text(widget.myText),
],
);
});
}
}
Flutter
Dart
I am a beginner in flutter and i am trying to add controller to streamBuilderWidget so i can dispose it but i have no idea where should i put the controller.. i tried this
the stream below as a widget not function
StreamController<QuerySnapshot> controller;
void dispose() {
super.dispose();
controller.close();
}
void initState() {
super.initState();
controller = StreamController<QuerySnapshot>();
}
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("users").doc(widget.documentUid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: circulearProgress(),
);
}
in this code it never disposed or closed the stream:(
Anyone who edits my code in the right way will be very grateful to him , thanks friends
StreamController is like a pipeline. In your case, that pipeline went from water supplier to your house, there is no need to worried about what goes in there.
But if you want to set up a pipeline from your washing machine to the draining hole, that is where you need to use StreamController.
Example:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final NumberController controller = NumberController();
#override
void dispose() {
controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
InputWidget(controller: controller,),
OutputWidget(controller: controller,)
],
),
),
),
);
}
}
class NumberController {
//This is the pipeline of "number"
final StreamController<int> controller = StreamController<int>.broadcast();
//This is where your "number" go in
Sink<int> get inputNumber => controller.sink;
//This is where your "number" go out
Stream<int> get outputNumber => controller.stream;
//Dispose
void dispose() {
controller.close();
}
}
class InputWidget extends StatelessWidget {
final NumberController controller;
const InputWidget({Key key, this.controller}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
controller.inputNumber.add(Random().nextInt(10));
},
child: Text(
'Random Number'
),);
}
}
class OutputWidget extends StatelessWidget {
final NumberController controller;
const OutputWidget({Key key, this.controller}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: controller.outputNumber,
builder: (context, snapshot) {
return Text(snapshot.hasData ? snapshot.data.toString() : 'No data');
},
);
}
}
You don't have to use StreamController. StreamBuilder you are using closes the stream internally.
From your comments, you seem to want to close the listener in the method below:
void handleDelete() {
FirebaseFirestore.instance.collection("handleCountM").doc(currentUser.uid + widget.documentUid).collection("handleCountM2").limit(1).snapshots()
.listen((value) {
value.docs.forEach((element) {
element.reference.delete();
});
});
}
You can do that by getting a reference to the stream subscription and calling .cancel on the subscription.
Calling .listen on a stream returns a stream subscription object like this:
StreamSubscription handleDeleteStreamSubscription = FirebaseFirestore.instance.collection("handleCountM").doc(currentUser.uid + widget.documentUid).collection("handleCountM2").limit(1).snapshots()
.listen((value) {
value.docs.forEach((element) {
element.reference.delete();
});
});
Cancelling the subscription is done like this:
handleDeleteStreamSubscription.cancel();
We want to show an AlertDialog after some asynchronous processing such as network processes.
When calling 'showAlertDialog ()' from an external class, I want to call it without context. Is there a good way?
class SplashPage extends StatelessWidget implements SplashView {
BuildContext _context;
#override
Widget build(BuildContext context) {
this._context = context;
...
}
I've considered the above method, but I'm worried about side issues.
Help
My current code
class SplashPage extends StatelessWidget implements SplashView {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyStoreColors.eats_white1_ffffff,
body: Center(
child: new SvgPicture.asset('assets/ic_splash.svg'),
),
);
}
#override
void showAlertDialog() {
showDialog<void>(
context: /*How to get context?*/,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
#override
void moveToHomeContainer() {
}
#override
void moveToLoginContainer() {
}
}
To show an AlertDialog you need the context, but in StatelessWidget you do not have access to it directly as in StatefulWidget.
Few options are [1]:
passing it as GlobalKey [2]
passing build context as parameter to any other function inside StatelessWidget
use a service to inject the dialog without context [3]
Cheers.
You should trigger rebuild when the async event complete, either convert your widget to StatefulWidget and call setState() or use a state management solution like Bloc.
For example using StatefulWidget your code will look like this:
class SplashPage extends StatefulWidget {
#override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> implements SplashView {
bool _asynOpDone = false;
/// Call this when the async operation is done.
void _onAsynOpDone() => setState(() => _asyncOpDone = true);
#override
Widget build(BuildContext context) {
if (_asyncOpDone) showAlertDialog(context);
return Scaffold(
...,
///
);
}
#override
void showAlertDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: ...,
);
}
}
You can apply Builder pattern concept to simplify this.
There is a little example here.
button_builder.dart
Good day. I've watched a video about Flutter's InheritedModel and got interested on it. Unfortunately, I can't seems to make it work properly.
Summary: Need help how to properly implement InheritedModel.
Expected Code Output: Widget CountText should not be updated when updating count parameter in CountModel.
Actual Code Output: CountText still updates (I think this is due to that the parent widget is a StatefulWidget)
Details
I am trying to implement a Counter app using InheritedModel. Code below is my code
import 'package:flutter/material.dart';
class CountModel extends InheritedModel<String> {
final int count;
CountModel({ this.count, child }) : super(child: child);
#override
bool updateShouldNotify(CountModel oldWidget) {
if (oldWidget.count != count) {
return true;
}
return false;
}
#override
bool updateShouldNotifyDependent(InheritedModel<String> oldWidget, Set<String> dependencies) {
if (dependencies.contains('counter')) {
return true;
}
return false;
}
static CountModel of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<CountModel>(context, aspect: aspect);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter',
theme: Theme.of(context),
home: Counter(),
);
}
}
class Counter extends StatefulWidget {
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// title: Text("Counter"),
),
body: CountModel(
count: count,
child: CounterText()
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
++count;
});
},
child: Icon(Icons.add),
),
);
}
}
class CounterText extends StatelessWidget {
#override
Widget build(BuildContext context) {
CountModel model = CountModel.of(context, 'test');
return Text('Count: ${model.count}');
}
}
I have a CountModel as InheritedModel and a CountText widget which consumes the data from the CountModel. As you can see in the implementation of the CountText, it pass test when getting the CountModel. In my understanding, it should not be updated when the count value is updated in the CountModel. Unfortunately, this does not happen.
In short, you should use const.
Add const to CounterText constructor
class CounterText extends StatelessWidget {
const CounterText();
...
}
and use const when you create instance of CounterText() (const CounterText())
class CounterState extends State<Counter> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: CountModel(..., child: const CounterText()),
...
);
}
}
And voila 🎉
I have described why this is happening here in details