Flutter Futurebuilder inside TabBarView not triggering the future after initial application load while switching between tabs? - flutter

I am new to flutter and trying to implement a tabview in homePage in the flutter app.
The first tab is populated from data from firebase remote config and second tab is populated by using Futurebuilder. When I switch the tabs the future function is not triggering. It is only triggered during initial application load. Whenever I switch tabs and come back to 2nd tab. The futurebuilder's future function is not triggered again.
Can someone give any solutions for this.?
Container(
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: GridView.count(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
padding: const EdgeInsets.all(4.0),
childAspectRatio: 1.0,
crossAxisCount: isTablet ? 2 : 1,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
children: [
FutureBuilder(
future: _getBookmarks,
builder:
(BuildContext context, AsyncSnapshot snapshot) {
var listWidget;
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data.length == 0) {
listWidget = Container(
child: Center(
child: Text("No Favorites to Display!"),
));
} else {
listWidget = ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final bookmarks = snapshot.data[index];
return BuildFavoriteCard(
bookmarks, context);
},
);
}
} else {
listWidget = Center(
child: CircularProgressIndicator(),
);
}
return listWidget;
})
],
))

Here's an example combining the TabBar and FutureBuilder examples of the Flutter documentation.
If you run this, you will see that a new future is created each time you navigate to the first tab (since the TabBarView's content is rebuilt).
I would assume that this is currently not working for you since your future _getBookmarks is defined somewhere higher up in the widget tree (in the part that is not rebuilt by switching tabs).
The solution would be to move the future inside your TabBarView widget.
import 'package:flutter/material.dart';
void main() {
runApp(const TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
const TabBarDemo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
],
),
title: const Text('Tabs Demo'),
),
body: TabBarView(
children: [
Center(
child: MyStatefulWidget(),
),
Icon(Icons.directions_transit),
],
),
),
),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key? key,
}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Future<String>? _calculation;
#override
void initState() {
_calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
super.initState();
}
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future:
_calculation, // calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
),
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = const <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
},
),
);
}
}

Related

Flutter Cubit fetching and displaying data

I'm trying to fetch data from Genshin API, code below is working, but only with delay (in GenshinCubit class), it looks weard, because I don't know how much time to set for delay. I think, there is a problem in code, cause it must not set the GenshinLoaded state before the loadedList is completed. Now, if I remove the delay, it just sets the GenshinLoaded when the list is still in work and not completed, await doesn't help. Because of that I get a white screen and need to hot reload for my list to display.
class Repository {
final String characters = 'https://api.genshin.dev/characters/';
Future<List<Character>> getCharactersList() async {
List<Character> charactersList = [];
List<String> links = [];
final response = await http.get(Uri.parse(characters));```
List<dynamic> json = jsonDecode(response.body);
json.forEach((element) {
links.add('$characters$element');
});
links.forEach((element) async {
final response2 = await http.get(Uri.parse(element));
dynamic json2 = jsonDecode(response2.body);
charactersList.add(Character.fromJson(json2));
});
return charactersList;
}
}
class GenshinCubit extends Cubit<GenshinState> {
final Repository repository;
GenshinCubit(this.repository) : super(GenshinInitial(),);
getCharacters() async {
try {
emit(GenshinLoading());
List<Character> list = await repository.getCharactersList();
await Future<void>.delayed(const Duration(milliseconds: 1000));
emit(GenshinLoaded(loadedList: list));
}catch (e) {
print(e);
emit(GenshinError());
}
}
}
class HomeScreen extends StatelessWidget {
final userRepository = Repository();
HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<GenshinCubit>(
create: (context) => GenshinCubit(userRepository)..getCharacters(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: Container(child: const CharactersScreen())),
),
);
}
}
class CharactersScreen extends StatefulWidget {
const CharactersScreen({
Key? key,
}) : super(key: key);
#override
State<CharactersScreen> createState() => _CharactersScreenState();
}
class _CharactersScreenState extends State<CharactersScreen> {
#override
Widget build(BuildContext context) {
return Column(
children: [
BlocBuilder<GenshinCubit, GenshinState>(
builder: (context, state) {
if (state is GenshinLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is GenshinLoaded) {
return SafeArea(
top: false,
child: Column(
children: [
Container(
color: Colors.black,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: state.loadedList.length,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 50.0, horizontal: 50),
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CharacterDetailsPage(
character: state.loadedList[index],
),
),
),
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.3),
borderRadius: const BorderRadius.all(
Radius.circular(
30,
),
)),
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(
right: 30.0, bottom: 30),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
state.loadedList[index].name
.toString(),
style: TextStyle(
color: Colors.black,
fontSize: 50),
),
RatingBarIndicator(
itemPadding: EdgeInsets.zero,
rating: double.parse(
state.loadedList[index].rarity
.toString(),
),
itemCount: int.parse(
state.loadedList[index].rarity
.toString(),
),
itemBuilder: (context, index) =>
Icon(
Icons.star_rate_rounded,
color: Colors.amber,
))
],
),
),
),
),
),
);
})),
),
],
),
);
}
if (state is GenshinInitial) {
return Text('Start');
}
if (state is GenshinError) {
return Text('Error');
}
return Text('Meow');
}),
],
);
}
}
I found a solution!
I've got that problem because of forEach. How to wait for forEach to complete with asynchronous callbacks? - there is a solution.

Flutter GridView in TabBar causing lag because of my code

There is a GridView in my TabBar which has two tabs and this tab is the 2nd one which is causing some UI lag. When I fetched some image in the GridView, I didn't experience any lag so I think I've done something wrong in my code which involves Futures which is loading videos from the internal storage directory. Also I have read about HandlerThread that it can help with the issue but I don't know how to implement it.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:thumbnails/thumbnails.dart';
import 'package:wastatusapp/utils/video_play.dart';
final Directory _videoDir =
Directory('/storage/emulated/0/WhatsApp/Media/.Statuses');
class VideoScreen extends StatefulWidget {
const VideoScreen({Key key}) : super(key: key);
#override
VideoScreenState createState() => VideoScreenState();
}
class VideoScreenState extends State<VideoScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
if (!Directory('${_videoDir.path}').existsSync()) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Install WhatsApp\n',
style: TextStyle(fontSize: 18.0),
),
const Text(
"Your Friend's Status Will Be Available Here",
style: TextStyle(fontSize: 18.0),
),
],
);
} else {
return VideoGrid(directory: _videoDir);
}
}
}
class VideoGrid extends StatefulWidget {
final Directory directory;
const VideoGrid({Key key, this.directory}) : super(key: key);
#override
_VideoGridState createState() => _VideoGridState();
}
class _VideoGridState extends State<VideoGrid> {
Future<String> _getImage(videoPathUrl) async {
//await Future.delayed(Duration(milliseconds: 500));
final thumb = await Thumbnails.getThumbnail(
videoFile: videoPathUrl,
imageType:
ThumbFormat.PNG, //this image will store in created folderpath
quality: 10);
return thumb;
}
#override
Widget build(BuildContext context) {
final videoList = widget.directory
.listSync()
.map((item) => item.path)
.where((item) => item.endsWith('.mp4'))
.toList(growable: false);
if (videoList != null) {
if (videoList.length > 0) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: GridView.builder(
itemCount: videoList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8,
),
itemBuilder: (context, index) {
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlayStatus(
videoFile: videoList[index],
),
),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Container(
child: FutureBuilder(
future: _getImage(videoList[index]),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.hasData) {
return Hero(
tag: videoList[index],
child: Image.file(
File(snapshot.data),
fit: BoxFit.cover,
),
);
} else {
return Center(
child: Column(
children: [
Text(
"Open WhatsApp with your internet on and then try again."),
Text(
"Make sure you have good internet connection!")
],
),
);
}
} else {
return Container(
child:
Image.asset('assets/images/video_loader.gif'),
);
}
}),
//new cod
),
),
);
},
),
);
} else {
return const Center(
child: Text(
'Sorry, No Videos Found.',
style: TextStyle(fontSize: 18.0),
),
);
}
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
}
}

Is there a way to push the updated state of data of one stateful widget into another stateful widget?

I have been struggling with the problem of pushing updated data from one widget to another. This problem occurs when I have two Stateful widgets and the data is updated in the parent Stateful widget but is not updated in the child Stateful widget. The error occurs with the usage of the freezed package but also occurs without it as well.
I have not been able to find anything that fixes this as of yet.
Below is an example:
First Stateful Widget:
class FirstWidget extends StatefulWidget {
#override
_FirstWidgetState createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
ItemBloc _itemBloc = getit<ItemBloc>();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
elevation: Mquery.width(context, 2.5),
backgroundColor: Colors.black
title: Text(
'First stateful widget',
style: TextStyle(fontSize: 17),
),
centerTitle: true,
),
body: BlocBuilder<ItemsBloc,ItemsState>(
cubit: _itemsBloc,
builder: (BuildContext context,ItemState state) {
return state.when(
initial: () => Container(),
loading: () => Center(child: CustomLoader()),
success: (_items) {
return AnotherStatefulWidget(
items: _items,
...
);
},
);
},
));
},
);
);
}
}
Second Stateful Widget:
class AnotherStatefulWidget extends StatefulWidget {
final List<String> items;
AnotherStatefulWidget(this.items);
#override
_AnotherStatefulWidgetState createState() => _AnotherStatefulWidgetState();
}
class _AnotherStatefulWidgetState extends State<AnotherStatefulWidget> {
final ScrollController scrollController = ScrollController();
ItemsBloc _itemsBloc = getit<ItemsBloc>();
bool _handleNotification(ScrollNotification notification, List<String> items) {
if (notification is ScrollEndNotification &&
scrollController.position.extentAfter == 0.00) {
_itemsBloc.add(ItemsLoadEvent.loadMoreItems(
categories: items, document: ...));
}
return false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: double.infinity,
height: 280,
child: Padding(
padding: EdgeInsets.only(
right: 8,
),
child: NotificationListener<ScrollNotification>(
onNotification: (_n) =>
_handleNotification(_n, widget.items),
child: DraggableScrollbar.arrows(
alwaysVisibleScrollThumb: true,
controller: scrollController,
child: ListView.builder(
controller: scrollController,
itemCount: widget.items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(16),
child: Align(
alignment: Alignment.center,
child: Text(
widget.items[index],
style: TextStyle(color: Colors.white),
)),
),
);
},
),
),
),
),
)
],
),
),
),
);
}
}
I would really appreciate any help!
Thank you for you time,
Matt

How to do a GridView with two crossAxisCount in Flutter?

This is the example of expected output
For the first row, the crossAxisCount will be 3 and the second row, the crossAxisCount will be 2.
GridView.builder(
physics: NeverScrollableScrollPhysics(),
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.75,
mainAxisSpacing: 2.0,
crossAxisSpacing: 1.0,
),
itemCount: int.parse(snapshot.data.result[num].collected),
itemBuilder:
(BuildContext context, int i) {
return Image.asset(
'assets/coin.png');
}),
You can set crossAxisCount to 1 and childAspectRatio to number you need, I use 2
In itemBuilder check index is Odd and return Row with 2 or 3 asset icon
You can copy paste run full code below
code snippet
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1, childAspectRatio: 2),
itemCount: _icons.length,
itemBuilder: (context, index) {
if (index.isOdd) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: Image.asset('assets/coin.png'),
),
Container(
child: Image.asset('assets/coin.png'),
),
Container(
child: Image.asset('assets/coin.png'),
),
],
);
} else {
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: InfiniteGridView()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class InfiniteGridView extends StatefulWidget {
#override
_InfiniteGridViewState createState() => new _InfiniteGridViewState();
}
class _InfiniteGridViewState extends State<InfiniteGridView> {
List<IconData> _icons = [];
#override
void initState() {
_retrieveIcons();
}
#override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1, childAspectRatio: 2),
itemCount: _icons.length,
itemBuilder: (context, index) {
if (index.isOdd) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: Image.asset('assets/coin.png'),
),
Container(
child: Image.asset('assets/coin.png'),
),
Container(
child: Image.asset('assets/coin.png'),
),
],
);
} else {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: Image.asset('assets/coin.png'),
),
Container(
child: Image.asset('assets/coin.png'),
),
],
);
}
});
}
void _retrieveIcons() {
Future.delayed(Duration(milliseconds: 200)).then((e) {
setState(() {
_icons.addAll([
Icons.ac_unit,
Icons.airport_shuttle,
Icons.all_inclusive,
Icons.beach_access,
Icons.cake,
Icons.free_breakfast
]);
});
});
}
}

How to shrink images in ListTile but not expand it?

I need to display images of several different sizes in a ListView.
When the image is larger than screen.width, I'd like it to shrink to fit width.
But when the image is shorter, I'd like it to keep its original size.
How can I do it? Thanks in advance.
I tried putting Image inside Flex, but couldn't "stop" the small one to expand.
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:firebase_database/firebase_database.dart';
void main() => runApp(MyApp());
const _imagesDir = "images";
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image List',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyListPage(title: 'Image List Page'),
);
}
}
class MyListPage extends StatefulWidget {
MyListPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyListPageState createState() => _MyListPageState();
}
class _MyListPageState extends State<MyListPage> {
Widget build1(BuildContext context, AsyncSnapshot snapshot) {
Widget _tileImagem(BuildContext context, String imageName) {
imageName = _imagesDir + "/"+ imageName;
return Padding(padding:EdgeInsets.all(2.0),
child: Flex(
direction: Axis.vertical,
children: <Widget>[
Image.asset(imageName),
]
),
);
}
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: AppBar(
title: Row(
children: <Widget> [
Padding(padding: EdgeInsets.only(right: 20.0),),
Text( 'Duda'),
]),
)
),
body: ListView(
shrinkWrap: true,
children: <Widget>[
Container(),
_tileImagem(context, 'flutter_big_medium.png'),
Container(), //My App have some different widgets
Container(),
Container(), //I kept them here just as place holder
Container(),
Container(),
Container(),
Container(),
Container(),
Divider(),
TileTexts(),
Divider(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () { },
child: Icon(Icons.skip_next),
),
);
}
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future:
FirebaseDatabase.instance.reference()
.child('Testing')
.once(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState){
case ConnectionState.done: return build1(context, snapshot);
case ConnectionState.waiting: return CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text("hasError: ${snapshot.error}");
} else {
return Text("${snapshot.data}");
}
}
}
);
}
}
class TileTexts extends StatefulWidget {
TileTexts() : super();
#override
_TileTextsState createState() => _TileTextsState();
}
class _TileTextsState extends State<TileTexts> {
#override
void initState() {
super.initState();
}
Widget text1(String title, String imageName, TextStyle style) {
return Expanded(
child:Container(
margin: const EdgeInsets.only(left: 10.0),
child: Column(
children: <Widget>[
Html(data: title,
useRichText: true,
defaultTextStyle: style,
),
((imageName == null))
? Container()
: Image.asset(_imagesDir + "/"+imageName),
]
),
),
);
}
Widget _tileDetail(BuildContext context, String imageName) {
return Container(
padding: EdgeInsets.fromLTRB(5.0,0.0,10.0,0.0),
child: Row(
children: <Widget>[
Material(
shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(22.0) ),
clipBehavior: Clip.antiAlias,
child: MaterialButton(
child: Text('X'),
color: Theme.of(context).accentColor,
elevation: 8.0,
height: 36.0,
minWidth: 36.0,
onPressed: () {
//
},
),
),
text1('<body>veja a imagem</body>', imageName, Theme.of(context).textTheme.caption),
],
),
);
}
//_TileTexts
#override
Widget build(BuildContext context) {
print('_TileTexts build');
return Column(
children: <Widget>[
_tileDetail(context, 'flutter_med_medium.png'),
Divider(),
_tileDetail(context, 'flutter_med_medium.png'),
Divider(),
_tileDetail(context, 'flutter_med_medium.png'),
],
);
}
}
Create an method,getTitleImage(imageName), that returns Flex if image is bigger then screen-with, else return the image inside an container or in other widget of choice.
....
return Padding(padding:EdgeInsets.all(2.0),
child: getTitleImage(imageName)
),
);
....
Here is some other tips and tricks using Flex
Please check the doc, it says:
The heights of the leading and trailing widgets are constrained according to the Material spec. An exception is made for one-line ListTiles for accessibility. Please see the example below to see how to adhere to both Material spec and accessibility requirements.
after reading docs, you should achieve what you want :)