Flutter - How to connect void to build widget? - flutter

I am trying to get a new historyTile() to be called to the Scaffold() each second. I am unsure how to make the void function connect.
Any advice and feedback is appreciated!
Code:
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
#override
void historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jm().format(now);
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
}
);
}
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (Timer t) => historyTile());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:
Container(
child: SingleChildScrollView(
child: historyTile(); // ERROR HERE
),
),
);
}
}

You can try creating periodic streams with a Stream Builder widget. If not, the simplest method is to put your widget in scaffold and try calling the setState function periodically with a 1-second timer.
In the StreamBuilder example you should change the widget a bit. Sending the parameter you want to update to the widget from outside will add a little more flexibility to you.
return Scaffold(
body: StreamBuilder<String>(
stream: Stream.periodic(const Duration(seconds: 1), (x) {
// Your Action Here
final now = DateTime.now();
return DateFormat.yMMMMd().add_jm().format(now);
}),
builder: (context, snapshot) {
String param = "";
if (snapshot.hasData) param = snapshot.data!;
return _historyTile(txt = param);
}
),
);
Or you could use your widget in Scaffold Body and periodically set the widgets state in timer callback.
class _activityTabState extends State<activityTab> {
String tileTime = "";
...
Timer.periodic(Duration(seconds: 1), () {
setState(() {
final now = DateTime.now();
tileTime = DateFormat.yMMMMd().add_jm().format(now);
});
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: historyTile(tileName);
),
),
);
}
or just
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('$tileTime'),
tileColor: Colors.greenAccent,
),
),
),
);
}
Create your historyTile widget as a custom widget
class HistoryTile extends StatefulWidget {
const HistoryTile({Key? key, required this.txt}) : super(key: key);
final String txt;
#override
State<HistoryTile> createState() => _HistoryTileState();
}
class _HistoryTileState extends State<HistoryTile> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text(widget.txt),
tileColor: Colors.greenAccent,
);
}
}

there is some issues in you ListView.Builder. You do not put itemCount there. And you need to use setState in timer. So codes are below. Please check.
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
String _now;
Timer _everySecond;
#override
historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jms().format(now);
return ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
});
}
void _timer() {
Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
_timer();
});
});
}
#override
void initState() {
super.initState();
_timer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: SingleChildScrollView(
child: historyTile(),
),
),
);
}
}

Related

how to prevent rebuild all widget in IndexedStack?

I want to rebuild just one widget in IndexedStack.
my app has 5 widgets(PostListScreen,AgendaListScreen ....)in IndexedStack.
PostListSceen have PostContainer.
I can go to PostContainer when I click element in PostListScreen. and then go back to PostListScreen.
5 widgets are all rebuild. I think just PostListScreen widget have to rebuild.
why other widgets also rebuild????. It makes unnecessary communication with server.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PreferredSizeWidget? _appBarContainer(bottomSelectedIndex) {
return (bottomSelectedIndex == 0 || bottomSelectedIndex == 1)
? const AppBarRedContainer()
: AppBarNavigatorContainer(actions: [
IconButton(
padding: EdgeInsets.only(right: 15.0.w),
icon: SvgPicture.asset(
"assets/images/notification_b.svg",
),
iconSize: 24.0.r,
constraints: const BoxConstraints(),
onPressed: () {
Beamer.of(context).beamToNamed('/$locationAlarm');
},
)
], title: (bottomSelectedIndex == 3 ? "핫글 모음" : "내 정보"))
as PreferredSizeWidget;
}
#override
Widget build(BuildContext context) {
int bottomSelectedIndex =
Provider.of<IndexNotifier>(context, listen: false).bottomSelectedIndex;
StockModel stock = Provider.of<StockCodeNotifier>(context, listen: false).stock;
if (stock.code == null) {
Provider.of<StockCodeNotifier>(context, listen: false).getStocks();
stock = Provider.of<StockCodeNotifier>(context, listen: false).stock;
FlutterNativeSplash.remove();
}
return Scaffold(
appBar: _appBarContainer(bottomSelectedIndex),
body: SafeArea(
child: IndexedStack(
index: bottomSelectedIndex,
children: [
PostListScreen(stock: stock),
AgendaListScreen(
stock: stock,
),
WriteScreen(
stockCode: stock.code!,
),
const HotListScreen(),
const MyInfoScreen()
],
),
),
);
}
}
postListScreen.dart
class PostListScreen extends StatefulWidget {
const PostListScreen({Key? key, required this.stock}) : super(key: key);
final StockModel stock;
#override
State<PostListScreen> createState() => _PostListScreenState();
}
class _PostListScreenState extends State<PostListScreen> {
late Future<List<PostModel>> _postData;
bool init = false;
#override
void initState() {
if(!init){
_postData = _fetchPostData();
init = true;
}
super.initState();
}
Future<List<PostModel>> _fetchPostData() async {
return await PostListService().getPosts(widget.stock.code!);
}
Future<void> _onRefresh() async {
setState(() {});
Future.value(null);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<PostModel>>(
future: _postData,
builder: (context, snapshot) {
return RefreshIndicator(
onRefresh: _onRefresh,
child: CustomScrollView(slivers: [
SliverAppBar(
floating: true,
expandedHeight: 148.0.h,
flexibleSpace: FlexibleSpaceBar(
background: StockInfo(
stockCount: widget.stock.count!,
stockName: widget.stock.name!,
),
),
),
(snapshot.hasData == false)
? const SliverFillRemaining(
child: Center(child: CircularProgressIndicator()))
: (snapshot.data!.isNotEmpty)
? _sliverList(snapshot.data!)
: SliverFillRemaining(
child: NoContentContainer(
onRefresh: _onRefresh,
noText: "이야기가 없어요",
offerText: '첫 이야기를 작성해 보시는 건 어떨까요?'),
),
]),
);
});
}
}

Update Text with Dissmissble setState

I want to update my Text() value whenever I dismiss an item from the screen .
This is the MainScreen() :
Text.rich(
TextSpan(
text: total().toString() + " DT",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold),
),
The function total() is located in Product Class like this :
class Product {
final int? id;
final String? nameProd;
final String? image;
final double? price;
Product({this.id, this.nameProd, this.image, this.price});
}
List<Product> ListProduitss = [
Product(
price: 100, nameProd: 'Produit1', image: 'assets/images/freedomlogo.png')
];
double total() {
double total = 0;
for (var i = 0; i < ListProduitss.length; i++) {
total += ListProduitss[i].price!;
}
print(total);
return total;
}
I have this in the main screen .
After I remove the item from list , I want to reupdate the Text() because the function is printing a new value in console everytime I dismiss a product :
This is from statefulWidget CartItem() that I render inside MainScreen() :
ListView.builder(
itemCount: ListProduitss.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Dismissible(
key: Key(ListProduitss.toString()),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
total();
// What to add here to update Text() value everytime
});
},
I tried to refresh the main screen but It didn't work .
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
MainScreen();
});
},
One way is to declare a local string variable to use within the text. Then initialise the variable using total() within initState(). Then in setState do the same process.
However, it may be beneficial for you to look into a state management pattern such as BLoC pattern. https://bloclibrary.dev/#/
late String text;
void initState() {
super.initState();
text = Product.total();
}
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
appBar: AppBar(),
body: Column(
children: [
Text(text),
ElevatedButton(child: Text("Update"), onPressed:() => setState(() {
text = Product.total();
}),)
],
)
);
}
I am going to add another example as there was confusion to the above example. Below is an example of updated a text field with the length of the list. It is updated every time an item is removed.
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> {
List<int> items = List<int>.generate(100, (int index) => index);
late String text;
#override
void initState() {
text = items.length.toString(); // << this is total;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
Expanded(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (BuildContext context, int index) {
return Dismissible(
background: Container(
color: Colors.green,
),
key: ValueKey<int>(items[index]),
onDismissed: (DismissDirection direction) {
setState(() {
items.removeAt(index);
text = items.length.toString(); // < this is total()
});
},
child: ListTile(
title: Text(
'Item ${items[index]}',
),
),
);
},
),
),
],
);
}
}

When I add elements to listview, how to update listview in Flutter?

I am new to flutter and I would like to add element every 5 seconds to my list view. I have list view and I think I have the true adding method. However, I do not know how to update my list view every 5 seconds.
void randomCity(){
List <int> colors = [yellow,green,blue,red,black,white];
List <String> countryNames = ["Gdańsk","Warszawa","Poznań","Białystok","Wrocław","Katowice","Kraków"];
List <String> countryImages = [gdanskPic,warszawaPic,poznanPic,bialystokPic,wroclawPic,katowicePic,krakowPic];
Random random = new Random();
DateTime now = new DateTime.now();
Future.delayed(Duration(seconds: 5), (){
setState(() {
int randomCity = random.nextInt(countryNames.length);
int randomColor = random.nextInt(colors.length);
countrylist.add(Country(
countryNames[randomCity], countryImages[randomCity],
colors[randomColor], now.toString()));
});
});
}
In this code I am adding new element to my list view.
randomCity();
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
backgroundColor: Colors.grey[100],
elevation: 0.0,
title: Text(
"Random City App",
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.add,
color: Colors.black,
size: 32,
),
onPressed: () {})
],
),
body: ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => CountryDetails(countryName: countrylist[index].name,
appBarColor: countrylist[index].color, date: countrylist[index].date, image: countrylist[index].image,))
);
},
title: Text(countrylist[index].name + " ${countrylist[index].date}"),
tileColor: Color(countrylist[index].color),
),
);
},
));
}
And this is my ListView.Builder.
You have to convert your widget into StatefulWidget and then rebuild it with setState (more info on ways to manage state https://flutter.dev/docs/development/data-and-backend/state-mgmt/options)
class MyApp extends StatelessWidget { // your main widget
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget { // create new StatefulWidget widget
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<Country> countrylist = []; // mover other variables in here
...
void randomCity(){
...
setState(() {}); // this will rebuild your widget again and again
}
#override
Widget build(BuildContext context) {
Future.delayed(Duration(seconds: 5), (){
randomCity();
});
return ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(countrylist[index]),
),
);
},
);
}
}
You have to tell the ListView to rebuild which you can do with the setState method (if you are using a StefulWidget). Also, I would use Timer instead of Future.delayed for periodic updates. Here would be a simplified example of your usecase:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
final countryNames = ['Germany', 'Croatia', 'Turkey', 'USA'];
List<String> countryList = [];
#override
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
int randomCity = Random().nextInt(countryNames.length);
countryList.add(countryNames[randomCity]);
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Updater'),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Card(
child: Text(countryList[index]),
);
},
itemCount: countryList.length,
),
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}

Animate resizing a ListView item on tap

I have a multi-line Text() inside a ListView item.
By default I only want to show 1 line. When the user taps this item i want it to show all lines. I achieve this by setting the maxLines property of the Text-Widget dynamically to 1 or null.
This works great, but the resizing occurs immediatly but I want to animate this transition.
Here is some example code:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
#override
void initState() {
_expanded = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
});
},
child: Text(
'Line1\nLine2\nLine3',
maxLines: _expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
),
);
}
}
I also already tried using an AnimatedSwitcher like this:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
Widget _myAnimatedWidget;
#override
void initState() {
_expanded = false;
_myAnimatedWidget = ExpandableText(key: UniqueKey(), expanded: _expanded);
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
_myAnimatedWidget =
ExpandableText(key: UniqueKey(), expanded: _expanded);
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: 2000),
child: _myAnimatedWidget,
),
);
}
}
class ExpandableText extends StatelessWidget {
const ExpandableText({Key key, this.expanded}) : super(key: key);
final expanded;
#override
Widget build(BuildContext context) {
return Text(
'Line1\nLine2\nLine3',
maxLines: expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
);
}
}
This animates the Text-Widget but the ListView-Row still resizes immediatly.
What is my mistake? Is the approach of setting the maxLines property maybe wrong for my problem?
Thanks for your help !
Have a great day !
Thanks to Joao's comment I found the right answer:
I just had to wrap my Widget inside the AnimatedSize() widget. That's all :)

Access state from parent - flutter

I try to create a basic notes app to study about flutter and I do not quite understand how to notify my NotesContainer that the button has been pressed. I tried to create a ref to it but the adding function is in the state class that I'm not sure how to reach.
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NotesContainer Notes = new NotesContainer();
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
Notes.add()
},
)
],
),
body: Notes
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return new _NotesContainer();
}
}
class _NotesContainer extends State<NotesContainer>{
final _notes = <NoteData>[new NoteData('title','thing to do'), new NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(new NoteData.noContent(title));
});
}
Widget build(BuildContext context){
return _buildNotesContainer();
}
_buildNotesContainer(){
return new ListView.separated(
itemCount: _notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(_notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
I guess the solution is somehow exposing the _function in the _NotesContainer via the stateful NotesContainer class. I wonder if there is a more elegant solution for this.
Thanks, Or
I think it makes more sense delegating the responsibility of adding a element further up in the widget tree. I modified your code to show how this works.
However, if you eventually get a deep widget tree and the children widgets require the _notes list, then I would recommend that you look into using a Inherited widget and add the _notes list to it, so you can access it without passing the state around too much.
import 'package:flutter/material.dart';
// Note the name change
class NotesPage extends StatefulWidget {
#override
_NotesPageState createState() => _NotesPageState();
}
class _NotesPageState extends State<NotesPage> {
final List<NoteData> _notes = <NoteData>[NoteData('title','thing to do'), NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(NoteData.noContent(title));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
add();
},
)
],
),
body: NotesContainer(notes: _notes)
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatelessWidget{
final List<NoteData> notes;
const NotesContainer({Key key, this.notes}) : super(key: key);
#override
Widget build(BuildContext context){
return ListView.separated(
itemCount: notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
Hope it helps :-)