Flutter - How to render a widget on button click? - android

Hi I'm new to flutter and I have an issue. I created simple app for better explanation. In my main.dart I call Button1() which is in button1.dart. When i press the button it should call Button2() in button2.dart. But the second button is not rendering. How can i do it? And how can i change some data in the button2.dart? For example change text of the button. I set text of the button to some variable and how can i pass it when i click the first button?
Thanks
My main.dart code
import 'package:flutter/material.dart';
import 'button1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
),
body: Center(
child: Column(
children: <Widget>[
Button1(),
],
),
),
);
}
}
My button1.dart code
import 'button2.dart';
class Button1 extends StatefulWidget {
#override
_Button1State createState() => _Button1State();
}
class _Button1State extends State<Button1> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("button1"),
onPressed: () {
setState(() {
Button2();
});
},
),
],
),
);
}
}
and here is my button2.dart code
class Button2 extends StatefulWidget {
#override
_Button2State createState() => _Button2State();
}
class _Button2State extends State<Button2> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("Button2"),
onPressed: () {},
),
],
),
);
}
}

I assume you are new to programming and I am trying to explain the concept here as easy as possible..
Let you have your main class (Parent). It contains your two widget/buttons (Children). To pass data from one children to another you can have a variable in the parent class and share your data through it. Here is an example..
class Parent{
String sharedData = "";
bool isVisible = false;
build(context){
//...
Child1((String newData){
setState(() {
sharedData = newData;
isVisible = true;
});
}),
if(isVisible) Child2(sharedData),
}
}
Here Child1 is using a callback to update the data. Inside setState it is updating the Parent class variable and also rebuilding the widget tree. Which updates the Child2 classes data.
Hope you got the point...

Related

Is it possible to update TextFormField using shared state (and without violating good practices)?

I am trying to make test project according to good practices.
Please note that I DON'T want any "hacky" approach. I am willing to learn good way of solving it.
My understanding of "lifting state up" is that any change updates the state, and then view is redrawn (rebuild) using current state. It is great in theory, but it DOES NOT work with TextFormField/TextEditingController.
I want to have a SharedState and bi-directonal TextFormField/TextEditingController, as follows:
case 1 (works):
TextFormField changes -> state is updated -> readonly Text (in WidgetTwo) is updated
case 2 (does not work):
button (in WidgetOne) is clicked -> state is updated -> TextFormField (in WidgetThree) shows new value from state
I have code in 3 different widgets + main file + SharedSate:
main.dart
void main() {
runApp(ChangeNotifierProvider(
create: (_) => sharedState(), child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
WidgetOne(),
WidgetTwo(),
WidgetThree(),
]),
),
);
}
}
shared_state.dart
class SharedState extends ChangeNotifier {
int counter = 0;
void setCounter(int c) {
counter = c;
notifyListeners();
}
void incrementCounter() {
counter++;
notifyListeners();
}
void decrementCounter() {
counter--;
notifyListeners();
}
Future fetchCounterFromWeb() async {
// simulate external call
await Future.delayed(Duration(milliseconds: 500));
setCounter(42);
}
}
widget_one.dart
class WidgetOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: false);
return Row(
children: [
ElevatedButton(
onPressed: () => state.decrementCounter(),
child: Text('decrement')),
ElevatedButton(
onPressed: () => state.incrementCounter(),
child: Text('increment')),
ElevatedButton(
onPressed: () => state.fetchCounterFromWeb(),
child: Text('fetch counter from web')),
],
);
}
}
widget_two.dart
class WidgetTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
return Row(
children: [Text('Value of counter is: ${state.counter}')],
);
}
}
widget_three.dart (problem is here)
class WidgetThree extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return WidgetThreeState();
}
}
class WidgetThreeState extends State<WidgetThree> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void initState() {
super.initState();
var state = Provider.of<SharedState>(context, listen: false);
_controller = TextEditingController(text: state.counter.toString());
}
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
// THE ISSUE:
// It is NOT possible to update Controller (or TextEditing field)
// without this hacky line (which is not good practice)
_controller.text = state.counter.toString();
return Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
onChanged: (v) {
state.setCounter(int.parse(v.isEmpty ? '0' : v));
},
)
]),
);
}
}
I know I can possible move TextEditingController to SharedState, but SharedState should be UI agnostic, and TextEditingController is a UI widget.

My elevated buttons are greyed out and i dont understand why

i think the on Pressed function in elevated button is null but i dont understand why
my main file where i am using List and Map to create and switch questions and answers
answers are on the buttons and they are printed on them but they are greyed out
import './quiz.dart';
import './result.dart';
void main() => runApp(TestApp());
#override
class TestApp extends StatefulWidget {
const TestApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _TestAppState();
}
}
class _TestAppState extends State<TestApp> {
var _i = 0;
final _question = const [
{
'q1': 'whats the capital of India',
'a1': ['Delhi', 'Mumbai', 'Chennai', 'Bangalore'],
},
{
'q1': 'whats the Language of India',
'a1': ['Sanskrit', 'Bengali', 'Hindi', 'Kannada'],
},
{
'q1': 'whats the continent India is located in',
'a1': ['Africa', 'Asia', 'America', 'Australia'],
},
{
'q1': 'whats second most spoken language in India',
'a1': ['Hindi', 'Gujarati', 'Marathi', 'English'],
},
];
_answeredQ() {
setState(() {
_i = _i + 1;
});
// return 0;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Test App!"),
),
body: _i < _question.length
? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i)
: Result(),
),
);
}
}
**here's my Quiz class using as a custom widget
import './questionText.dart';
import './answer.dart';
class Quiz extends StatelessWidget {
final List<Map<String, Object>> qMap;
final aFunction;
final int index;
Quiz({required this.qMap, required this.aFunction, required this.index});
#override
Widget build(BuildContext context) {
return Column(
children: [
Question(
qMap[index]['q1'],
),
...(qMap[index]['a1'] as List<String>).map((ans) {
return AnswerW(aFunction, ans);
}).toList()
],
);
}
}
and here's the button custom widget class
class AnswerW extends StatelessWidget {
final selAns;
final String answerText;
AnswerW( this.selAns, this.answerText);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.all(10),
child: ElevatedButton(onPressed: selAns,
child: Text(answerText),
),
);
}
}
In ? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i) You are passing the return value of _answeredQ(), not the actual function itself. You can change this to just _answeredQ (without the "()") or aFunction: () => _answeredQ()
FWIW It's good in dart to take advantage of strong typing. It provides you with better error messages and better linting. Because you don't have any types for most of your variables they can be anything, and the linter has a hard time trying to figure out if you have a type mismatch.

Flutter - setState to another class

I just started programming in Flutter. I want to create an app to keep track of the expiration dates of food.
My app is composed of:
main.dart that returns a MaterialApp class with inside the Home
home.dart that contains the AppBar, a Scaffold which contains a ListBuilder() as body and a FAB which should add a new item.
list_builder.dart that contains the stateful widget ListBuilder which takes a list of Strings from items_list.dart and creates a ListView with some tiles
items_list.dart that contains a List of Strings, a function to remove, add and retrieve the list.
What I made so far is a list of items with a trailing trash icon button that deletes the single item from the list. All works as expected.
Now I want that pressing the FAB, it triggers the ItemsList.addItem() which adds an item to the list. That works, of course, but the list on screen (created by the list_builder.dart) is not updated unless I delete one item.
I tried unsuccessfully to use callback functions, I'm sure I'm missing something.
This is the code:
main.dart
import 'package:flutter/material.dart';
import 'home.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Quando Scade?',
home: Home(),
theme: ThemeData(
primarySwatch: Colors.lightGreen,
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:quando_scade/items_list.dart';
import 'list_builder.dart';
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
list_builder.dart
import 'package:flutter/material.dart';
import 'items_list.dart';
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: ItemsList.getItems().length,
itemBuilder: (context, index) {
return _buildRow(ItemsList.getItems()[index], index);
},
);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.removeItem(index);
});
},
),
);
}
}
items_list.dart
class ItemsList {
static List<String> _items = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
// to add items
static void addItem(String name) => ItemsList._items.add(name);
// to remove item
static void removeItem(int i) => ItemsList._items.removeAt(i);
// returns the list of items
static List<String> getItems() {
return ItemsList._items;
}
}
I see you are using setState on item delete, you should use it when adding an item as well:
...
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
ItemsList.addItem('ciao');
});
print('item added!!!');
},
child: const Icon(Icons.add),
),
...
Of course, make the Home widget as StatefulWidget before that.
Though this is not an optimal solution to what you're looking for, however, it's better than changing your Home class to a stateful widget and rebuilding your entire widget tree. I have modified your code to make it work exactly the way you want it without calling the setState function.
home.dart
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.itemList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
items_list.dart
class ItemsList {
final _list = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
StreamController<List<String>> _items =
StreamController<List<String>>.broadcast();
Stream<List<String>> get items => _items.stream;
// to add items
void addItem(String name) {
//_reOpenStream();
_list.add(name);
_items.sink.add(_list);
// _items.close();
}
// to remove item
void removeItem(int i) {
//_reOpenStream();
_list.removeWhere((element) => element == _list[i]);
_items.sink.add(_list);
}
void close() {
_items.close();
}
static final ItemsList _singleton = ItemsList._internal();
static ItemsList get itemList => ItemsList();
factory ItemsList() {
return _singleton;
}
ItemsList._internal();
}
list_builder.dart
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return StreamBuilder<List<String>>(builder: (_, snapshot) {
int itemCount = snapshot.data.length;
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
//Handle the empty list by replacing the container widget with your logic
return itemCount <=0 ? Container(): _buildRow(snapshot.data[index], index);
},
);
}, initialData: [],);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.itemList.removeItem(index);
});
},
),
);
}
}
Only call the close() method when you no longer need the stream, otherwise, it'd throw a bad state error when you try accessing it again. Additionally, as you progress in your learning try refactoring your codes to use an architecture design.. Peace!

Flutter: change state of body from fab

I have an app where I cant to add a new item on ListView by clicking on FAB.
But I want fab and body of MetarialApp be in other classes. I don't want to smash them in one.
I'm trying to change count of children for ListView in Stateful widget, using Notification. But it doesn't work.
How to communicate with different widgets (like add an item to ListView widget by clicking on fab)?
What's the best approach? I've heard about global keys but I don't nderstand how to use them.
main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var list = MyList();
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text("My App")),
body: list,
floatingActionButton: FloatingActionButton(
onPressed: () {
MyNotification(count: 1).dispatch(context);
},
child: Icon(Icons.add)),
),
theme: ThemeData(primarySwatch: Colors.green),
);
}
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => ListState();
}
class ListState extends State {
int count = 3;
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onCountPush,
child: ListView.builder(
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return BodyCard();
}),
);
}
bool onCountPush(MyNotification notify) {
setState(() {
count += notify.count;
});
return true;
}
}
class MyNotification extends Notification {
final int count;
const MyNotification({this.count});
}
body and FAB are properties of Scaffold. So when you are trying to control the state of the body from FAB, the one that should be handling it is not the body but the Scaffold itself. Look, the Scaffold extends StatefulWidget and on the other hand the MyList extends StatelessWidget. Hope you get my point.
main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyScaffold(),
theme: ThemeData(primarySwatch: Colors.green),
);
}
}
class MyScaffold extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyScaffoldState();
}
class MyScaffoldState extends State {
int count = 3;
void changeCount() {
setState(() {
count = count == 3 ? 5 : 3;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My App")),
body: MyList(count),
floatingActionButton: FloatingActionButton(
onPressed: changeCount,
child: Icon(Icons.add),
),
);
}
}
class MyList extends StatelessWidget {
final int count;
const MyList(this.count);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: const EdgeInsets.all(10),
height: 30,
color: Colors.red,
);
},
);
}
}
Note:
Later when your states get more complex, you don't wanna stick with using setState to manage the states. Like others said, you can learn BLoC, ChangeNotifier or anything that suits you.
you should use Provider or BLoc in your code so you can do that

Flutter setState of child widget without rebuilding parent

I have a parent that contain a listView and a floatingActionButton i would like to hide the floatingActionButton when the user starts scrolling i have managed to do this within the parent widget but this requires the list to be rebuilt each time.
I have moved the floatingActionButton to a separate class so i can update the state and only rebuild that widget the problem i am having is passing the data from the ScrollController in the parent class to the child this is simple when doing it through navigation but seams a but more awkward without rebuilding the parent!
A nice way to rebuild only a child widget when a value in the parent changes is to use ValueNotifier and ValueListenableBuilder. Add an instance of ValueNotifier to the parent's state class, and wrap the widget you want to rebuild in a ValueListenableBuilder.
When you want to change the value, do so using the notifier without calling setState and the child widget rebuilds using the new value.
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
ValueNotifier<bool> _notifier = ValueNotifier(false);
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
ValueListenableBuilder(
valueListenable: _notifier,
builder: (BuildContext context, bool val, Widget? child) {
return Text(val.toString());
}),
],
);
}
#override
void dispose() {
_notifier.dispose();
super.dispose();
}
}
For optimal performance, you can create your own wrapper around Scaffold that gets the body as a parameter. The body widget will not be rebuilt when setState is called in HideFabOnScrollScaffoldState.
This is a common pattern that can also be found in core widgets such as AnimationBuilder.
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return HideFabOnScrollScaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, i) => ListTile(title: Text('item $i')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
controller: controller,
);
}
}
class HideFabOnScrollScaffold extends StatefulWidget {
const HideFabOnScrollScaffold({
Key key,
this.body,
this.floatingActionButton,
this.controller,
}) : super(key: key);
final Widget body;
final Widget floatingActionButton;
final ScrollController controller;
#override
State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}
class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
bool _fabVisible = true;
#override
void initState() {
super.initState();
widget.controller.addListener(_updateFabVisible);
}
#override
void dispose() {
widget.controller.removeListener(_updateFabVisible);
super.dispose();
}
void _updateFabVisible() {
final newFabVisible = (widget.controller.offset == 0.0);
if (_fabVisible != newFabVisible) {
setState(() {
_fabVisible = newFabVisible;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: widget.body,
floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
);
}
}
Alternatively you could also create a wrapper for FloatingActionButton, but that will probably break the transition.
I think using a stream is more simpler and also pretty easy.
You just need to post to the stream when your event arrives and then use a stream builder to respond to those changes.
Here I am showing/hiding a component based on the focus of a widget in the widget hierarchy.
I've used the rxdart package here but I don't believe you need to. also you may want to anyway because most people will be using the BloC pattern anyway.
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class _PageState extends State<Page> {
final _focusNode = FocusNode();
final _focusStreamSubject = PublishSubject<bool>();
Stream<bool> get _focusStream => _focusStreamSubject.stream;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
_focusStreamSubject.add(_focusNode.hasFocus);
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildVeryLargeComponent(),
StreamBuilder(
stream: _focusStream,
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text("keyboard has focus")
}
return Container();
}),
)
],
),
);
}
}
You can use StatefulBuilder and use its setState function to build widgets under it.
Example:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
// put widget here that you do not want to update using _setState of StatefulBuilder
Container(
child: Text("I am static"),
),
StatefulBuilder(builder: (_context, _setState) {
// put widges here that you want to update using _setState
return Column(
children: [
Container(
child: Text("I am updated for $count times"),
),
RaisedButton(
child: Text('Update'),
onPressed: () {
// Following only updates widgets under StatefulBuilder as we are using _setState
// that belong to StatefulBuilder
_setState(() {
count++;
});
})
],
);
}),
],
);
}
}

Categories

Resources