Flutter setState of child widget without rebuilding parent - android

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++;
});
})
],
);
}),
],
);
}
}

Related

how create a RIVE animation with flutter

I want to create a RIVE animation with flutter. I followed a tutorial in YouTube. I wrote the same thing but when I execute two errors is displayed
(RiveFile.import (data);
file.mainArtboard;)
Here is the code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Using Rive'),
),
body: RocketContainer());
}
}
class RocketContainer extends StatefulWidget {
#override
_RocketContainerState createState() => _RocketContainerState();
}
class _RocketContainerState extends State<RocketContainer> {
Artboard _artboard;
RiveAnimationController _rocketController;
#override
void initState() {
_loadRiveFile();
super.initState();
}
void _loadRiveFile() async {
final bytes = await rootBundle.load('assets/rocket.riv');
final file = RiveFile.import(bytes);
setState(() {
_artboard = file.mainArtboard;
});
}
void _launch() async {
_artboard.addController(
_rocketController = SimpleAnimation('launch'),
);
setState(() => _rocketController.isActive = true);
}
void _fall() async {
_artboard.addController(
_rocketController = SimpleAnimation('fall'),
);
setState(() => _rocketController.isActive = true);
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 250,
child: _artboard != null
? Rive(
artboard: _artboard,
fit: BoxFit.cover,
)
: Container()),
TextButton(onPressed: () => _launch(), child: Text('launch')),
TextButton(onPressed: () => _fall(), child: Text('fall'))
],
);
}
}
errors:
The current Dart SDK version is 2.10.5.
Because animation depends on cupertino_icons >=1.0.1 which requires SDK version >=2.12.0-0 <3.0.0, version solving failed.
pub get failed (1; Because animation depends on cupertino_icons >=1.0.1 which requires SDK version >=2.12.0-0 <3.0.0, version solving failed.)
*error: Instance member 'import' can't be accessed using static access. (static_access_to_instance_member at [animation] lib\main.dart:47)
*error: The getter 'mainArtboard' isn't defined for the type 'bool'. (undefined_getter at [animation] lib\main.dart:50)
You could have a look at the example provided with the updated and latest documentation of Rive in their official Github repository.
Control playing and pausing a looping animation:
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class PlayPauseAnimation extends StatefulWidget {
const PlayPauseAnimation({Key? key}) : super(key: key);
#override
_PlayPauseAnimationState createState() => _PlayPauseAnimationState();
}
class _PlayPauseAnimationState extends State<PlayPauseAnimation> {
// Controller for playback
late RiveAnimationController _controller;
// Toggles between play and pause animation states
void _togglePlay() =>
setState(() => _controller.isActive = !_controller.isActive);
/// Tracks if the animation is playing by whether controller is running
bool get isPlaying => _controller.isActive;
#override
void initState() {
super.initState();
_controller = SimpleAnimation('idle');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
controllers: [_controller],
// Update the play state when the widget's initialized
onInit: () => setState(() {}),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _togglePlay,
tooltip: isPlaying ? 'Pause' : 'Play',
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
To play an animation from an asset bundle, use:
RiveAnimation.asset('assets/vehicles.riv'
in place of
RiveAnimation.network('https://cdn.rive.app/animations/vehicles.riv',
This line:
_controller = SimpleAnimation('idle');
attempts to play an animation called 'idle'. If your animation is named differently, try replacing the name here.

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 - How to render a widget on button click?

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...

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: 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

Categories

Resources