I am learning flutter and have some experience in javascript.
I want to add length of _suggestions to _appBar.
I know I need setState(), but I can't find the right place to insert setState().
When I add setState in build(), the flutter framework issues error.
I understand the setState requires the build to be called, so if setState() is in build(), the condition is recursive.
And the ListView.builder seems to have no event handler. If there is event handler, I can register setState() there.
// Copyright 2018 The Flutter team. 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:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
String title = 'Startup Name Generator';
final _appBar = const CustomAppBar();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
const CustomAppBar({Key? key}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
String title = "Title";
_changeTitle(String title) {
setState(() {
this.title = title;
});
}
#override
Widget build(BuildContext context) {
return AppBar(
title: Text(title),
);
}
}
flutter codelab
Tested this code. but not works.
// Copyright 2018 The Flutter team. 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:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length.toString()),
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState(()
{
_suggestions.addAll(generateWordPairs().take(10));
});
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, required this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title: Row(
children: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions),
],
),
);
}
}
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length);,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState((){
_suggestions.addAll(generateWordPairs().take(10));
})
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, requiered this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title:Row(
chidren: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions)
],
),
);
}
}
This may help you. Welcome to flutter
Related
I have a simple flutter application. It's ok, but I'm trying to understand how onHover: (event){...} works, why "event" contains data? How can I make my own widget have function parameters like that?
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double dx = 0, dy = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
home: Scaffold(
body: MouseRegion(
onHover: (event) {
setState(() {
dx = event.localPosition.dx;
dy = event.localPosition.dy;
});
},
child: Center(
child: Text('$dx'),
),
),
),
);
}
}
To create your own onChange, or the like we can use ValueChanged.
For example, taking a look at the code for a TextButton() we see:
const TextButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
ValueChanged<bool>? onHover,
the onHover uses a ValueChanged.
You can implement your own valueChanged using this example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Buttons(
onHover: (value) {
// Do something
print(value);
},
),
),
),
);
}
}
class Buttons extends StatelessWidget {
final ValueChanged<String> onHover;
Buttons({Key? key, required this.onHover}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: () {
onHover('Pressed');
},
child: Text("Click me")),
Text('hi')
],
);
}
}
So this how we pass the data from the widget which is at the bottom of the widget tree.
It's more related to passing the value from bottom to top using callback functions.
Below is the simple example to demonstrate this data sharing.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _parentData = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
"Parent State Value: " + _parentData.toString(),
),
ChildWidgetExample(
callbackFn: (data) {
setState(() {
_parentData = data;
});
},
)
],
);
}
}
class ChildWidgetExample extends StatefulWidget {
final Function(int) callbackFn;
const ChildWidgetExample({
Key? key,
required this.callbackFn,
}) : super(key: key);
#override
State<ChildWidgetExample> createState() => _ChildWidgetExampleState();
}
class _ChildWidgetExampleState extends State<ChildWidgetExample> {
int data = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
data.toString(),
),
const SizedBox(
height: 30,
),
ElevatedButton(
onPressed: () {
setState(() {
data++;
});
widget.callbackFn(data);
},
child: const Text("Press"),
)
],
);
}
}
In Flutter you can declare Functions with parameters.
void Function(String foo) myFunction;
So you declare in as a variable in your widget component.
MyWidget({required this.myFunction});
Then when you have to call this component you can write :
...
child : MyWidget(myFunction: (String foo) {},),
I'm new to flutter. I'm trying to make a simple automatically updating time.
I tried with RefreshIndicator but it didn't work for me. What is the correct way to make it update per second? Is it possible to make it update with the setState in the bottomNavigationBar by making recursion function?
enter image description here
import 'dart:async';
import 'package:flutter/material.dart';
int Currentindex = 0 ;
late String time1;
var today = DateTime.now();
String time()
{
today = DateTime.now();
time1 = (today.hour.toString()+" : "+today.minute.toString()+" : "+today.second.toString());
return time1;
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp( debugShowCheckedModeBanner : false ,
home: Firstpage()
,);
}
}
class Firstpage extends StatefulWidget {
const Firstpage({Key? key}) : super(key: key);
#override
_FirstpageState createState() => _FirstpageState();
}
class _FirstpageState extends State<Firstpage> {
#override
Widget build(BuildContext context) {
return MaterialApp( debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Currentindex == 0 ? Column(mainAxisAlignment: MainAxisAlignment.center, children: [ElevatedButton(onPressed: (){
setState(() {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context)
{
return SecondPage();
}
)
);
});
}, child: Text("Click me"))],
) : Currentindex == 1 ? Column(mainAxisAlignment : MainAxisAlignment.center,children: [Text(time(),style: TextStyle(fontSize: 80),)], ):
SizedBox()
) ,
bottomNavigationBar: BottomNavigationBar(items: const [
BottomNavigationBarItem(label: "Icecream",icon: Icon(Icons.icecream , color: Colors.white,)),
BottomNavigationBarItem(label: "Time",icon: Icon(Icons.access_time , color: Colors.white,))],
backgroundColor: Colors.blue,
onTap: (int index){setState(() {
if(Currentindex == 1){today = DateTime.now();;}
Currentindex = index;
});},
),
),
);
}
}
class SecondPage extends StatefulWidget {
const SecondPage({Key? key}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return MaterialApp( debugShowCheckedModeBanner: false,
home: Scaffold( backgroundColor: Colors.grey,
appBar: AppBar(title: Text("Cool"),backgroundColor: Colors.transparent,),
body: Center(
child: Column(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ElevatedButton(onPressed: (){
setState(() {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){return Firstpage();
}
)
);
}
);
}, child: Text("Go back"), style: ElevatedButton.styleFrom(primary: Colors.yellow , onPrimary: Colors.orange),)],
),
),
)
);
}
}
See if it helps. Ideally this kind of widget (that updates all the time) should be in leafs and by themselves, to avoid rebuilding parts of your tree unnecessarily.
class MyWidget extends StatefulWidget {
const MyWidget();
#override
State<StatefulWidget> createState() => _MyWidget();
}
class _MyWidget extends State<MyWidget> {
String lastTime = '';
#override
initState() {
super.initState();
timeUpdate.listen((time) => setState(() => lastTime = time ));
}
String get time => DateTime.now().toString();
Stream<String> get timeUpdate async* {
while(true) {
await Future.delayed(Duration(seconds: 1));
yield time;
}
}
#override
Widget build(BuildContext context) {
return Text(
time,
style: Theme.of(context).textTheme.headline4,
);
}
}
If you're trying to make a digital clock looking thing, try this:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ClockWidget extends StatelessWidget {
const ClockWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, snapshot) {
return Text(
DateFormat('HH:mm:ss')
.format(DateTime.now().toLocal())
.toString(),
);
},
);
}
}
I have a list of integers. Each of this item is displayed in a statefull widget by iterating the list in the build method.
import 'package:flutter/material.dart';
import 'package:widget_list/ItemWidget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Item list state demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Item list state demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static int itemsCount = 0;
final List<int> _items = List.empty(growable: true);
void _add() {
setState(() {
_items.add(itemsCount++);
});
}
void _remove() {
setState(() {
_items.removeAt(0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () => _add(),
child: const Text('Add item'),
),
TextButton(
onPressed: () => _items.isNotEmpty ? _remove() : null,
child: const Text('Remove item'),
),
],
),
for (var item in _items) ItemWidget(item: item),
],
),
),
);
}
}
Each of this widget, has a statically incremented integer "id" in it's state. Both the item and the widget id are displayed.
import 'package:flutter/material.dart';
var widgetCount = 0;
class ItemWidget extends StatefulWidget {
final int item;
const ItemWidget({
required this.item,
Key? key,
}) : super(key: key);
#override
State<ItemWidget> createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
final int widgetId = widgetCount++;
#override
Widget build(BuildContext context) {
print("Item ${widget.item} / Widget $widgetId");
return Text("Item ${widget.item} / Widget $widgetId");
}
}
When I add an item in the list, it is displayed in a newly generated widget. E.g. first item 0 is displayed in widget 0.
But if I remove an item at the beginning of the list (e.g. item 0), it's not the first widget that is destoyed, but the last one. The item 1 is then displayed in widget 0.
The widget item is final, so it cannot change. The widget ids are still the same, so the states were not rebuild. Then, why are the states no more consistent with the widgets?
This is done in FLutter desktop for Linux, v3.0.1
In the itemWidget you are creating a value from 0 so for each element that is rendered it will start from 0. please check the code below
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Item list state demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Item list state demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static int itemsCount = 0;
final List<ItemInfo> _items = List.empty(growable: true);
void _add() {
setState(() {
itemsCount++;
_items.add(ItemInfo(itemsCount, itemsCount));
});
}
void _remove() {
setState(() {
_items.removeAt(0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () => _add(),
child: const Text('Add item'),
),
TextButton(
onPressed: () => _items.isNotEmpty ? _remove() : null,
child: const Text('Remove item'),
),
],
),
for (var item in _items) ItemWidget(item: item),
],
),
),
);
}
}
and Itemwidget to be like this
class ItemWidget extends StatefulWidget {
final ItemInfo item;
const ItemWidget({
required this.item,
Key? key,
}) : super(key: key);
#override
State<ItemWidget> createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
#override
Widget build(BuildContext context) {
return Text(
"Item ${widget.item.itemVal} / Widget ${widget.item.itemIndex}");
}
}
also I created a class named ItemInfo which will hold both the value and its index.
class ItemInfo {
int itemVal;
int itemIndex;
ItemInfo(this.itemVal, this.itemIndex);
}
I followed this excellent Riverpod tutorial. In the last steps the author uses the following code:
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
and
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
I tried using _buttonState and _timeLeftProvider and, from what I see, the app works correctly. So, my questions are:
What need is there to create and use buttonProvider and timeLeftProvider?
How many Providers are really needed?
Thank you very much!
2020-10-26 UPDATE (main.dart code and output image)
My main.dart code is:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final timeLeft = useProvider(timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(buttonProvider);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
If I click on the ‘Play’ button and then let the 10 seconds pass, in the end I get the same result in the 2 cases:
2020-10-27 UPDATE (main.dart code without using buttonProvider and timeLeftProvider)
This is the output even if buttonProvider and timeLeftProvider are not used, like in the following main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
// final buttonProvider = Provider<ButtonState>((ref) {
// return ref.watch(_buttonState);
// });
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
// final timeLeftProvider = Provider<String>((ref) {
// return ref.watch(_timeLeftProvider);
// });
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final timeLeft = useProvider(_timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(_buttonState);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
What am I doing wrong?
Those providers are used to prevent unnecessary rebuilds but aren't fundamentally necessary. Only create providers you need - especially as these providers will never be disposed of in the app lifecycle, they are just wasted space. However, preventing unnecessary rebuilds should be the top priority.
In the linked article, the author is utilizing a workaround recommended by the package author to prevent rebuilds when listening to a specific attribute of a StateNotifier. So, for now, that is the most efficient way to accomplish the task. I will try to update this answer if new functionality is introduced to solve it.
I would refer to the package creator's examples for more context.
Here's a quick example of why you might use multiple providers to cache responses from an external API:
class ExampleApiRepository {
ExampleApiRepository(this._read);
static final provider = Provider((ref) => ExampleApiRepository(ref.read));
final Reader _read;
Future<Example> search(String query) async {
final response = await _call('api/example/$query');
return Example.fromJson(response.data);
}
}
final searchExample = FutureProvider.family<Example, String>((ref, query) async {
return ref.watch(ExampleApiRepository.provider).search(query);
});
In this example, if the same query is passed to the searchExample provider, it will return the previously fetched result. Could this be achieved without multiple providers? Yes - and for most cases this will hold true. Creating a provider is about convenience and efficiency. So don't be afraid to use many providers, but don't create them for the sake of creating them.
That said, the article you linked is informative and appreciated.
I'm trying to implement home screen quick actions / app shortcuts in my Flutter app. What I'm trying to achieve is when the user launches my app via a quick action, the app changes the selected tab inside the bottom navigation bar. Any help is appreciated.
main.dart:
runApp(
MaterialApp(
theme: Themes.appLightTheme,
darkTheme: Themes.appDarkTheme,
home: QuickActionsController(
child: HomeFrame(currentIndex: 0),
),
My QuickActionsController class:
import 'package:binfinder/screens/HomeFrame.dart';
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
class QuickActionsController extends StatefulWidget {
final HomeFrame child;
QuickActionsController({Key key, this.child}) : super(key: key);
#override
_QuickActionsControllerState createState() => _QuickActionsControllerState();
}
class _QuickActionsControllerState extends State<QuickActionsController> {
final QuickActions quickActions = QuickActions();
int _currentIndex = 0;
#override
void initState() {
super.initState();
_handleQuickActions();
_setupQuickActions();
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_map',
localizedTitle: 'Map',
),
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_map') {
setState(() {
_currentIndex = 1;
});
} else {
setState(() {
_currentIndex = 0;
});
}
});
}
#override
Widget build(BuildContext context) {
widget.child.currentIndex = _currentIndex;
return widget.child;
}
}
In the demo below, direct click app will enter First Page and In Quick Action choose Main view will enter Second Page
_handleQuickActions need to use
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
and use initial index to control page index
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
full code
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'dart:io';
class QuickActionsManager extends StatefulWidget {
final Widget child;
QuickActionsManager({Key key, this.child}) : super(key: key);
_QuickActionsManagerState createState() => _QuickActionsManagerState();
}
class _QuickActionsManagerState extends State<QuickActionsManager> {
final QuickActions quickActions = QuickActions();
#override
void initState() {
super.initState();
_setupQuickActions();
_handleQuickActions();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_main',
localizedTitle: 'Main view',
icon: Platform.isAndroid ? 'quick_box' : 'QuickBox'),
ShortcutItem(
type: 'action_help',
localizedTitle: 'Help',
icon: Platform.isAndroid ? 'quick_heart' : 'QuickHeart')
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_main') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
} else if (shortcutType == 'action_help') {
print('Show the help dialog!');
}
});
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QuickActions Demo',
home: QuickActionsManager(child: BottomNavigationBarController(initialIndex: 0,)));
}
}
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Home')));
}
}
class Login extends StatelessWidget {
const Login({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Login')));
}
}
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
final List<Widget> pages = [
FirstPage(
key: PageStorageKey('Page1'),
),
SecondPage(
key: PageStorageKey('Page2'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('First Page')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('Second Page')),
],
);
#override
void initState() {
_selectedIndex = widget.initialIndex;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
demo, emulator is a little slow when enter Second Page