Flutter Cubit Web - flutter

For retrieve items from FireStore and for pick image i am using cubit.
Cubit:
class ItemCubit extends Cubit<ItemState> {
ItemCubit(this._dataBase)
: super(ItemInitial());
final DataBase _dataBase;
StreamSubscription streamSubscription;
Future<void> pickItemImg() async {
final currentTempImg =
await ImagePickerWeb.getImage(outputType: ImageType.bytes);
emit(ItemImgPicked(currentTempImg));
}
Future getItem() async {
streamSubscription = _dataBase.getItem().listen((data) {
emit(ItemLoaded(data));
});
}
}
State:
#immutable
abstract class ItemState {}
class ItemLoaded extends ItemState {
final List<Item> item;
ItemLoaded(this.item);
}
class ItemImgPicked extends ItemState {
final Uint8List currentTempImg;
ItemImgPicked(this.currentTempImg);
}
Page with blocbuilders
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
RaisedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) => Dialog(
child: Container(
width: 400,
child: OutlineButton(
onPressed: () async {
context.bloc<ItemCubit>().pickProductImg();
},
child: BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemImgPicked) {
return Image.memory(state.currentTempImg);
} else {
return Container();
}
},
),
),
),
),
);
},
child: Text('add'),
),
BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemLoaded) {
return Column(
children: state.item.map(
(item) {
return Text(item.name);
},
).toList(),
);
}
return CircularProgressIndicator();
},
)
],
),
);
}
}
Issue is when on show dialog I picked image, the picked image is displayed, but at the same time on main page blocbuilder for item list return CircularProgressIndicator. if I use hot reload at this time, after it shows me the list of item. It looks like the state for picked image replace state for item list. How to solve it?

Your main page bloc builder listens for ItemLoaded which you never emit as far as I can tell. You can put a breakpoint into that line, it should not get hit.
That said, please treat your async functions better, you missed to await some futures, that might not be your problem now, but it will become a problem sooner or later.

Related

Future builder runs forever, if memoizer used doesnt notify to the listerners

I am trying to switch the drawer tab, according to the value stored in shared preferences using the following code.
code works fine when memoizer is not used but future builder runs forever.
If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets.
I need some way to stop running future builder forever and notify users as well accordingly by triggering get and set functions present in it
Notifier class
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
Drawer
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return Container(
width: 260,
child: Drawer(
child: Material(
color: Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Container(
padding: AppLandingView.padding,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.data == true) {
return _buildMenuItem(
text: 'widget1',
icon: Icons.add_business,
onTap: () {
provider.switchApp(false);
},
);
} else {
return _buildMenuItem(
text: 'widget2',
icon: Icons.add_business,
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
),
],
),
),
),
);
},
),
);
}
Scaffold
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: _buildDrawer(),
);
}
Update
I analysed further, problem lies in provider.getValue(), if i use notifyListeners() before returning the value future builder runs forever
I removed it and the future builder doesn't run forever, but it doesn't update other widgets.
Scenario is
widget 1
contains a drawer
has a button to switch app
on tap value is set using shared preferences (setValue() function) and listeners are notified
in widget 1 notifier is working well and changing the drawer button option when setValue() is called on tap.
everything resolved in widget 1, as its calling setValue() hence notifyListeners() is triggered and widget1 is rerendered
widget 2
only gets value from shared preferences(getValue() function). getValue function cant use notifyListeners(), if used futurebuilder is running forever
widget 2 don't set any value so it doesn't use setValue() hence it's not getting notified
how I can notify widget 2, when on tap setValue() is triggered in widget 1
i.e widget1 sets the app using setValue() function
widget2 gets value from getValue() function and get notified
Update 2
class SwitchAppProvider with ChangeNotifier {
dynamic _myValue;
dynamic get myValue => _myValue;
set myValue(dynamic newValue) {
_myValue = newValue;
notifyListeners();
}
setValue(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
SwitchAppProvider(){
getValue();
}
Future<void> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
myValue = prefs.getBool('key');
}
}
widget 2
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
if (provider.myValue == true) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: Text('provider.myValue'));
}
})
);
}
}
_buildMenuItem
// helper widget to build item of drawer
Widget _buildMenuItem({
required String text,
required IconData icon,
required GestureTapCallback onTap,
}) {
final color = Colors.white;
final hoverColor = Colors.white;
return ListTile(
leading: Icon(icon, color: color),
title: Text(text, style: TextStyle(color: color, fontSize: 18)),
hoverColor: hoverColor,
onTap: onTap,
);
}
"If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets."
That is the expected behaviour:
An AsyncMemoizer is used when some function may be run multiple times in order to get its result, but it only actually needs to be run once for its effect.
so prefs.setBool('key', value); is executed only the first time.
You definitely do not want to use it.
If you edit your code to remove the AsyncMemoizer, we can try to help you further.
Edit after Update
You are right, the getValue() function should not notify listeners, if it does that, then the listeners will rebuild and ask for the value again, which will notify listeners, which will rebuild and ask for the value again, which... (you get the point).
There is something wrong in your reasoning. widget1 and widget2 are not notified, the Consumer is notified. Which will rebuild everything. The code is quite complicated and it could be simplified a lot by removing unneeded widgets.
I will suggest you to
await prefs.setBool('isWhatsappBusiness', value); before notifying listeners.
have a look at this answer for a similar problem.
Edit 3
I do not know what you are doing wrong, but this works:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
),
);
}
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
},
),
);
}
}
If you still cannot get it working, then the problem is somewhere else.
Edit 4
First, I suggest you to be more clear in future questions. Write all the code that is needed immediately and remove widgets that are not needed. Avoid confusion given by naming different things in the same way.
The second widget does not update because it is listening to a different notifier.
When you do
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
in Widget2 you are creating a new provider object, you are not listening to changes in the provider you created in the Drawer.
You need to move the ChangeNotifierProvider.value widget higher in the widget tree, and use the same one:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
body: const Widget2(),
),
),
);
}
Widget _buildDrawer() {
return Consumer<SwitchAppProvider>(builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
});
}
}
class Widget2 extends StatelessWidget {
const Widget2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
return FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('Am I building even more ?');
if (snapshot.data == true) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return const Text('provider.myValue');
}
},
);
},
);
}
}

How to show loading spinner in GetBuilder

In FutureBuilder when working with an API you can easily show loading spinner when data is not yet available with this code,
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}
how do I do same for GetBuilder when using getx as state management library?
Here's a basic example of re-building based on the value of an isLoading bool. I'm just changing the value of a String but this should give you the idea of doing a proper API call in a GetX function and displaying an indicator. While I typically default to using GetBuilder whenever possible, showing loading indicators I generally just use Obx so I don't have to call update() twice.
class TestController extends GetxController {
bool isLoading = false;
String data = '';
Future<void> fetchData() async {
isLoading = true;
update(); // triggers the GetBuilder rebuild
await Future.delayed(
const Duration(seconds: 2),
() => data = 'Data Loaded',
);
isLoading = false;
update();
}
}
You can test this by throwing this in a Column. Just make sure the controller is initialized first at some point with Get.put(TestController());
GetBuilder<TestController>(
builder: (controller) => controller.isLoading
? CircularProgressIndicator()
: Text(controller.data)),
ElevatedButton(
onPressed: () => controller.fetchData(),
child: Text('Fetch Data'),
),
If you don't want to have to manually call the function you can also lose the isLoading bool use a FutureBuilder but then just pass a Future function from a GetX class to keep that logic out of your UI.
Update
Here's an example using live dummy data of random Kanye quotes from
https://api.kanye.rest Copy the code below into your IDE and run it and it should make sense.
Basic ApiCaller class
class ApiCaller extends GetConnect {
final url = 'https://api.kanye.rest';
Future<String> fetchData() async {
final response = await httpClient.get(url);
return response.body['quote'] as String;
}
}
Updated TestController class
class TestController extends GetxController {
String data = 'no data';
bool isLoading = false;
Future<void> updateData() async {
_updateIsLoading(true);
final apiCaller = ApiCaller();
await Future.delayed(
const Duration(seconds: 1),
() => data = 'Data Loaded',
); // just to add more visible time with loading indicator
data = await apiCaller.fetchData();
_updateIsLoading(false);
}
void _updateIsLoading(bool currentStatus) {
isLoading = currentStatus;
update();
}
}
Example with GetBuilder and FutureBuilder
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.put(TestController());
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FutureBuilder(
future: ApiCaller().fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('FutureBuilder: ${snapshot.data}');
} else {
return CircularProgressIndicator();
}
},
),
GetBuilder<TestController>(
builder: (_) => controller.isLoading
? CircularProgressIndicator()
: Text('GetBuilder: ${controller.data}'),
),
ElevatedButton(
onPressed: () => controller.updateData(),
child: Text('Update GetBuilder'),
),
],
),
),
),
);
}
}
Example with FutureBuilder with function from GetX class

How to add a CircularProgressIndicator while navigating to a PDF View Page

In the code below, I am reading a pdf file from my images folder then viewing it in a new page. But when I navigate to the new page it takes time to load the pdf. So, I want to add a CircularProgressIndicator while transitioning. I can't figure out how to do it. How do I add a CircularProgressIndicator widget?
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_full_pdf_viewer/flutter_full_pdf_viewer.dart';
class PdfPage extends StatefulWidget {
#override
_PdfPageState createState() => _PdfPageState();
}
class _PdfPageState extends State<PdfPage> {
Future<String> makeFile() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempFile = File('$tempPath/copy.pdf');
ByteData bd = await rootBundle.load('images/dummy.pdf');
await tempFile.writeAsBytes(bd.buffer.asUint8List(), flush: true);
return tempFile.path;
}
bool isLoading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
makeFile().then((value) {
setState(() {
path = value;
print(path);
//isLoading = false;
});
});
}
String path;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
child: Text("Open PDF Screen"),
onPressed: () {
// CircularProgressIndicator(value: true,);
Navigator.push(context,
MaterialPageRoute(builder: (context) => PDFScreen(path)));
},
),
),
);
}
}
class PDFScreen extends StatelessWidget {
final String path;
// final bool isLoading;
PDFScreen(
this.path,
);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: path);
}
}
You can use FutureBuilder class which is precisely made for this. That is, some action is expected in the future and you would like to show a progress indicator meanwhile. The action may actually complete or return an error. Futurebuilder lets you handle all these scenarios.
You can shift makefile() into class PDFScreen and use widget build method to return the FutureBuilder. The simplest implementation is below.
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: makeFile(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
} else {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: snapshot.data
);
}
},
);
}
You might want to visit the official docuementation or this blog on Medium.
One way is to use it in an Dialog.
static Future showDialog(BuildContext context, GlobalKey _key) async {
return showLoadingDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return SimpleDialog(
key: _key,
children: <Widget>[
Center(
child: Container(
child: Row(
children: <Widget>[
CircularProgressIndicator(),
SizedBox(
height:10,
width:10,
),
Text("Please Wait!"),
]
),
],
),
);
}
);
}
Here GlobalKey is used to Show or hide the dialog created.
To call this simple initialize a GlobalKey in the class as GlobalKey<State> _dialogKey = GlobalKey<State>();.
Then you can show the dialog as:
showLoadingDialog(context, _dialogKey);
And when you want it to hide, just call:
Navigator.of(_dialogKey.currentContext,rootNavigator: true).pop();
This is just one way of using CircularProgressIndicator in an SimpleDialog, Hope this helps.

pull down to REFRESH in Flutter

My dashboard code looks like this,
Here I am doing get req in getReport method, I have added the RefreshIndicator in the code which when pulled down inside container should do the refresh, there I am calling my getData(), But I am not getting the refreshed content, I am adding my code below, let me know if anywhere I made a mistake.
below my dashboard.dart
class Window extends StatefulWidget {
#override
_WindowState createState() => _WindowState();
}
class _WindowState extends State<Window> {
Future reportList;
#override
void initState() {
super.initState();
reportList = getReport();
}
Future<void> getReport() async {
http.Response response =
await http.get(reportsListURL, headers: {"token": "$token"});
switch (response.statusCode) {
case 200:
String reportList = response.body;
var collection = json.decode(reportList);
return collection;
case 403:
break;
case 401:
return null;
default:
return 1;
}
}
getRefreshScaffold() {
return Center(
child: RaisedButton(
onPressed: () {
setState(() {
reportList = getReport();
});
},
child: Text('Refresh, Network issues.'),
),
);
}
getDashBody(var data) {
double maxHeight = MediaQuery.of(context).size.height;
return Column(
children: <Widget>[
Container(
height: maxHeight - 800,
),
Container(
margin: new EdgeInsets.all(0.0),
height: maxHeight - 188,
child: new Center(
child: new RefreshIndicator( //here I am adding the RefreshIndicator
onRefresh:getReport, //and calling the getReport() which hits the get api
child: createList(context, data),
),),
),
],
);
}
Widget createList(BuildContext context, var data) {
Widget _listView = ListView.builder(
itemCount: data.length,
itemBuilder: (context, count) {
return createData(context, count, data);
},
);
return _listView;
}
createData(BuildContext context, int count, var data) {
var metrics = data["statistic_cards"].map<Widget>((cardInfo) {
var cardColor = getColorFromHexString(cardInfo["color"]);
if (cardInfo["progress_bar"] != null && cardInfo["progress_bar"]) {
return buildRadialProgressBar(
context: context,
progressPercent: cardInfo["percentage"],
color: cardColor,
count: cardInfo["value"],
title: cardInfo["title"],
);
} else {
return buildSubscriberTile(context, cardInfo, cardColor);
}
}).toList();
var rowMetrics = new List<Widget>();
for (int i = 0; i < metrics.length; i += 2) {
if (i + 2 < metrics.length)
rowMetrics.add(Row(children: metrics.sublist(i, i + 2)));
else
rowMetrics.add(Row(children: [metrics[metrics.length - 1], Spacer()]));
}
return SingleChildScrollView(
child: LimitedBox(
// maxHeight: MediaQuery.of(context).size.height / 1.30,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: rowMetrics,
),
),
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: reportList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
var data = snapshot.data;
if (snapshot.hasData && !snapshot.hasError) {
return getDashBody(data);
} else if (data == null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Timeout! Log back in to continue"),
Padding(
padding: EdgeInsets.all(25.0),
),
RaisedButton(
onPressed: () {
setState(() {
token = null;
});
Navigator.of(context).pushReplacement(
CupertinoPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
child: Text('Login Again!'),
),
],
),
);
} else {
getRefreshScaffold();
}
}
},
);
}
}
Basic Example
Below is a State class of a StatefulWidget, where:
a ListView is wrapped in a RefreshIndicator
numbersList state variable is its data source
onRefresh calls _pullRefresh function to update data & ListView
_pullRefresh is an async function, returning nothing (a Future<void>)
when _pullRefresh's long running data request completes, numbersList member/state variable is updated in a setState() call to rebuild ListView to display new data
import 'package:flutter/material.dart';
import 'dart:math';
class PullRefreshPage extends StatefulWidget {
const PullRefreshPage();
#override
State<PullRefreshPage> createState() => _PullRefreshPageState();
}
class _PullRefreshPageState extends State<PullRefreshPage> {
List<String> numbersList = NumberGenerator().numbers;
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: numbersList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(numbersList[index]),
);
},),
),
);
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
numbersList = freshNumbers;
});
// why use freshNumbers var? https://stackoverflow.com/a/52992836/2301224
}
}
class NumberGenerator {
Future<List<String>> slowNumbers() async {
return Future.delayed(const Duration(milliseconds: 1000), () => numbers,);
}
List<String> get numbers => List.generate(5, (index) => number);
String get number => Random().nextInt(99999).toString();
}
Notes
If your async onRefresh function completes very quickly, you may want to add an await Future.delayed(Duration(seconds: 2)); after it, just so the UX is more pleasant.
This gives time for the user to complete a swipe / pull down gesture & for the refresh indicator to render / animate / spin indicating data has been fetched.
FutureBuilder Example
Here's another version of the above State<PullRefreshPage> class using a FutureBuilder, which is common when fetching data from a Database or HTTP source:
class _PullRefreshPageState extends State<PullRefreshPage> {
late Future<List<String>> futureNumbersList;
#override
void initState() {
super.initState();
futureNumbersList = NumberGenerator().slowNumbers();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: futureNumbersList,
builder: (context, snapshot) {
return RefreshIndicator(
child: _listView(snapshot),
onRefresh: _pullRefresh,
);
},
),
);
}
Widget _listView(AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index]),
);
},);
}
else {
return Center(
child: Text('Loading data...'),
);
}
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
futureNumbersList = Future.value(freshNumbers);
});
}
}
Notes
slowNumbers() function is the same as in the Basic Example above, but the data is wrapped in a Future.value() since FutureBuilder expects a Future, but setState() should not await async data
according to RĂ©mi, Collin & other Dart/Flutter demigods it's good practice to update Stateful Widget member variables inside setState() (futureNumbersList in FutureBuilder example & numbersList in Basic example), after its long running async data fetch functions have completed.
see https://stackoverflow.com/a/52992836/2301224
if you try to make setState async, you'll get an exception
updating member variables outside of setState and having an empty setState closure, may result in hand-slapping / code analysis warnings in the future
Not sure about futures, but for refresh indicator you must return a void so
Use something like
RefreshIndicator(
onRefresh: () async {
await getData().then((lA) {
if (lA is Future) {
setState(() {
reportList = lA;
});
return;
} else {
setState(() {
//error
});
return;
}
});
return;
},
Try this and let me know!
EDIT:
Well, then just try this inside you refresh method
setState(() {
reportList = getReport();
});
return reportList;
Try this:
onRefresh: () {
setState(() {});
}}
instead of onRefresh:getReport
reportList field is Future which returns its value once. So, when you call getReport again it changes nothing. Actually, more correctly it'll be with Stream and StreamBuilder instead of Future and FutureBuilder. But for this code it can be shortest solution
Easy method: you can just use Pull Down to Refresh Package - https://pub.dev/packages/pull_to_refresh
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I am working on a huge project which contains CustomScrollView, NestedScrollView, ListView, etc I tried every answer above and all of the answers use RefreshIndicator from flutter SDK. It doesn't work entirely with my app because I also have horizontal scroll views. So in order to implement it I had to use NestedScrollView on almost every screen. Then I came to know about liquid_pull_to_refresh, applied it to the top widget, and WOLAAH! If you need a separate logic for each screen then use it at the top of each screen but in my case, I'm refreshing the whole project's data.

How to call dispose when using BLoC pattern and StatelessWidget

I am trying to understand BLoC pattern but I cannot figure out where or when to call dispose() in my example.
I am trying to understand various state management techniques in Flutter.
I came up with an example I managed to build with the use of StatefulWidget, scoped_model and streams.
I believe I finally figured out how to make my example work with the use of "BloC" pattern but I have a problem with calling the dispose() method as I use the StatelessWidgets only.
I tried converting PageOne and PageTwo to StatefulWidget and calling dispose() but ended up with closing the streams prematurely when moving between pages.
Is it possible I should not worry at all about closing the streams manually in my example?
import 'package:flutter/material.dart';
import 'dart:async';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<ThemeData>(
initialData: bloc.themeProvider.getThemeData,
stream: bloc.streamThemeDataValue,
builder: (BuildContext context, AsyncSnapshot<ThemeData> snapshot) {
return MaterialApp(
title: 'bloc pattern example',
theme: snapshot.data,
home: BlocPatternPageOne(),
);
},
);
}
}
// -- page_one.dart
class BlocPatternPageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(block pattern) page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go to page two'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return BlocPatternPageTwo();
},
),
);
},
);
}
}
// -- page_two.dart
class BlocPatternPageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(bloc pattern) page two'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go back to page one'),
onPressed: () {
Navigator.of(context).pop();
},
);
}
}
// -- bloc.dart
class SwitchProvider {
bool _switchValue = false;
bool get getSwitchValue => _switchValue;
void updateSwitchValue(bool value) {
_switchValue = value;
}
}
class ThemeProvider {
ThemeData _themeData = ThemeData.light();
ThemeData get getThemeData => _themeData;
void updateThemeData(bool value) {
if (value) {
_themeData = ThemeData.dark();
} else {
_themeData = ThemeData.light();
}
}
}
class Bloc {
final StreamController<bool> switchStreamController =
StreamController.broadcast();
final SwitchProvider switchProvider = SwitchProvider();
final StreamController<ThemeData> themeDataStreamController =
StreamController();
final ThemeProvider themeProvider = ThemeProvider();
Stream get streamSwitchValue => switchStreamController.stream;
Stream get streamThemeDataValue => themeDataStreamController.stream;
void sinkSwitchValue(bool value) {
switchProvider.updateSwitchValue(value);
themeProvider.updateThemeData(value);
switchStreamController.sink.add(switchProvider.getSwitchValue);
themeDataStreamController.sink.add(themeProvider.getThemeData);
}
void dispose() {
switchStreamController.close();
themeDataStreamController.close();
}
}
final bloc = Bloc();
At the moment everything works, however, I wonder if I should worry about closing the streams manually or let Flutter handle it automatically.
If I should close them manually, when would you call dispose() in my example?
You can use provider package for flutter. It has callback for dispose where you can dispose of your blocs. Providers are inherited widgets and provides a clean way to manage the blocs. BTW I use stateless widgets only with provider and streams.
In stateless widget, there is not dispose method so you need not to worry about where to call it.
It's as simple as that