I have the "standard" build code:
if (listOfLessons.length == 28)
return MaterialApp(...);
else
return MaterialApp(
theme: theme,
home: Scaffold(
appBar: AppBar(
title: Text('Getting Data from Belgium!'),
),
body: Container(
child: Center(
child: CircularProgressIndicator(),
)
),
),
);
I have this in initState()
#override
void initState() {
super.initState();
_listOfLessons = lessonController.getLessons();
setState(() {});
}
getLessons
List<Lesson> getLessons() {
List<Lesson> listOfLessons = [];
FirebaseLessonRepository lessonRepository = FirebaseLessonRepository();
var flistOfLessons = lessonRepository.fbgetLessons();
flistOfLessons.forEach((collectionSnapshot) {
collectionSnapshot.docs.forEach((documentSnapshot) {
FbLesson fbLesson = FbLesson.fromSnapshot(documentSnapshot);
List<DateTime> classOfferings = [];
fbLesson.classOfferings!.forEach((fbClassOffering) {
classOfferings.add(
DateTime.fromMillisecondsSinceEpoch(
fbClassOffering.seconds * 1000));
});
Lesson lesson = Lesson();
lesson.lessonCode = fbLesson.lessonCode;
lesson.prerequisite = fbLesson.prerequisite;
lesson.description = fbLesson.description;
lesson.online = fbLesson.online;
lesson.sequence = fbLesson.sequence;
lesson.classOfferings = classOfferings;
listOfLessons.add(lesson);
});
});
return listOfLessons;
}
FirebaseLessonRepository
class FirebaseLessonRepository {
static FirebaseFirestore _firebaseFirestore = FirebaseFirestore.instance;
final CollectionReference _lessonCollection
= FirebaseFirestore.instance.collection('Lessons');
Stream<QuerySnapshot> fbgetLessons() {
return _lessonCollection.snapshots();
}
}
From stepping through the debugger I see that in initState(),
_listOfLessons = lessonController.getLessons() returns before the query is finished loading the data, and then calls setState() right away.
I am not using Bloc or any other plugin other than firebase and dart:async;
My felling is that I should use a stream in getLessons() to trigger a setState();
something like this
class LessonsSpeaker {
final StreamController _streamController = StreamController<List<Lesson>>();
Stream<List<Lesson>> get stream {
return _streamController.stream as Stream<List<Lesson>>;
}
speak(List<Lesson> listOfLessons) async {
_streamController.add(listOfLessons);
_streamController.close();
}
}
But I am not sure how and where to invoke it.
Thanks for reading this long post.
Related
In a personal flutter project am working with Getx state management whereby am fetching data from a parse server to then save it to my local db on the app. Everything works fine in the backend in terms of fetching the data and later saving it locally as you can see in my controller class below.
However once I get the data from the server I want to display my custom progress but the ui is frozed with the circular progress and when it unfreezes the activity that was to update the progress is done at 99%. Am using Drift a wrapper around Sqlite.
class ProgressController extends GetxController {
String classid = '';
int progressValue = 0;
List<Student>? students = [];
MyDatabase? db;
#override
void onInit() {
super.onInit();
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {
super.onClose();
}
/// Get the list of students from the server
Future<List<Student>?> fetchStudentsData() async {
bool isConnected = await hasReliableInternetConnectivity();
if (isConnected) {
final EventObject eventObject = await httpGet(
client: http.Client(),
url: '${ApiConstants.students}?where={"class":$classid}&order=studentid&limit=1000',
);
try {
if (eventObject.id == EventConstants.requestSuccessful) {
final StudentsResponse jsonResponse = StudentsResponse.fromJson(
json.decode(eventObject.response),
);
students = jsonResponse.results;
if (students!.isNotEmpty) {
saveStudentsData();
} else {
showToast(
text: "No data was found",
state: ToastStates.error,
);
}
}
} catch (exception) {
students = null;
}
} else {
showToast(
text: "You don't seem to have reliable internet connection",
state: ToastStates.error,
);
students = null;
}
update();
return students;
}
/// Save students data to the local db
Future<void> saveStudentsData() async {
for (int i = 0; i < students!.length; i++) {
int progress = (i / students!.length * 100).toInt();
if (progress > progressValue) {
progressValue = progress;
update();
}
db!.saveNewStudent(students![i]);
}
update();
Get.offAll(() => HomeView());
}
}
class ProgressView extends StatelessWidget {
final ProgressController controller = Get.put(ProgressController());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
margin: const EdgeInsets.all(30),
child: GetBuilder<ProgressController>(
builder: (controller) => FutureBuilder<List<Student>?>(
future: controller.fetchStudentsData(),
builder: (BuildContext context,
AsyncSnapshot<List<Student>?> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!.isNotEmpty) {
return LineProgress(
progressSize: 100,
progressVl: controller.progressValue,
borderColor: Colors.black,
progressColor: AppColors.primaryColor,
backgroundColor: AppColors.secondaryColor,
);
} else {
return const Text('You are not connected to the internet');
}
} else if (snapshot.hasError) {
return const Text('An unexpected error occured');
} else {
return const CircularProgress();
}
},
),
),
),
),
);
}
}
My ui in the save view is as shown in the 2 frames below
The first frame is when fetching data from the server and the second is when the data has been received and saved to the db. It simply freezes until when it's done.
I try to use FutureBuilder in Flutter to wait ulti my initState is finished then buil the UI for the app.
But when the app is running, the screen keep rebuilding each time I press another button (the button does totally different thing).
Future loadUser() async {
String jsonString = await storage.read(key: "jwt");
final jsonResponse = json.decode(jsonString);
loggedUser = new LoggedUser.fromJson(jsonResponse);
print(loggedUser.token);
getProfile();
getJourneyByUserId()
.then((receivedList){
addRanges(receivedList);});
}
Future<List<Journey>>getJourneyByUserId() async {
var res = await http.get(
Uri.parse("$baseUrl/journeys/userid=${loggedUser.user.userId}"),
headers: {
'Content_Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ${loggedUser.token}',
},
);
if (res.statusCode == 200) {
print("Get journeys successfully");
}
var data = jsonDecode(res.body);
List idList = [];
for (var i in data) {
idList.add(i["journeyId"]);
}
for (var i in idList) {
var res = await http.get(
Uri.parse("$baseUrl/journeys/$i"),
);
var data = jsonDecode(res.body);
Journey userJourney = new Journey.fromJson(data);
setState(() {
journeyList.add(userJourney);
});
}
print("Journey ${journeyList.length}");
return journeyList;
}
addRanges(journeyList){
setState(() {
rangeList=[];
});
if (journeyList.isNotEmpty) {
for (var i in journeyList) {
DateTime startDate =
DateTime(i.startDate.year, i.startDate.month, i.startDate.day);
DateTime endDate =
DateTime(i.endDate.year, i.endDate.month, i.endDate.day);
setState(() {
rangeList.add(PickerDateRange(startDate, endDate));
});
}
}
print("Range ${rangeList.length}");
return rangeList;
}
returnRange() {
List<PickerDateRange> list = [];
for(int i =0; i<rangeList.length;i++){
list.add(rangeList[i]);
}
return list;
}
Future functionForBuilder() async {
return await returnRange();
}
//initState function
#override
void initState() {
super.initState();
loadUser();
functionForBuilder();
}
//build the UI
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("$_name's Profile",style: TextStyle(color: kColorPalette4),),
centerTitle: true,
),
body: Container(
child: FutureBuilder(
future: functionForBuilder(),
builder: (BuildContext context,AsyncSnapshot snapshot){
//here I set the condition for each case of snapshot
}
I have read some documents say that I should assign the functionForBuilder() to a Future variable when initState then use it in the future child of FutureBuilder. Example:
Future _future;
//initState function
#override
void initState() {
super.initState();
loadUser();
_future=functionForBuilder();
}
// then with the FutureBuilder
future: _future
With this way the screen is not rebuild anymore but my function returnRange() seems like not running as my expextation (I called the returnRange() once in the build() function).
Thanks in advance for your answer!
Whenever you assign to the _future variable again, you must do that inside a setState block, otherwise the widget will not rebuild with the new future.
For example:
void updateData() {
setState(() {
_future = functionForBuilder();
});
}
If you use FutureBuilder, it rebuild items again and again.
Try two ways:
Don't use `future: functionForBuilder(), comment it.
Remove FutureBuilder(), simply use Container().
And let me know any issue?
Code:
call your future in the initstate method not in the build as shown in the example.
class MyPage extends StatefulWidget { #override State<MyPage> createState() => _MyPageState(); } class _MyPageState extends State<MyPage> { // Declare a variable. late final Future<int> _future; #override void initState() { super.initState(); _future = _calculate(); // Assign your Future to it. } // This is your actual Future. Future<int> _calculate() => Future.delayed(Duration(seconds: 3), () => 42); #override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder<int>( future: _future, // Use your variable here (not the actual Future) builder: (_, snapshot) { if (snapshot.hasData) return Text('Value = ${snapshot.data!}'); return Text('Loading...'); }, ), ); } }
I am developing a mobile application using Flutter. I am using the flutter bloc package, https://pub.dev/packages/flutter_bloc for managing and setting up the bloc. But when the state change it is not updating the widgets or views.
I have a bloc class file called home_bloc.dart with the following implementation.
class HomeEvent {
static const int FETCH_ARTICLES = 1;
static const int TOGGLE_IS_FILTERING = 2;
int _event = 0;
String _filterKeyword = "";
int get event => _event;
void set event(int event) {
this._event = event;
}
String get filterKeyword => _filterKeyword;
void set filterKeyword(String filterKeyword) {
this._filterKeyword = filterKeyword;
}
}
class HomeBloc extends Bloc<HomeEvent, HomeState> {
Repository _repository = Repository();
HomeState state = HomeState();
#override
HomeState get initialState => state;
#override
Stream<HomeState> mapEventToState(HomeEvent event) async* {
switch (event.event) {
case HomeEvent.FETCH_ARTICLES:
{
List<dynamic> articles = List<dynamic>();
fetchArticles(filter: event.filterKeyword).listen((dynamic article) {
articles.add(article);
});
state = state.copyWith(articles: articles);
break;
}
case HomeEvent.TOGGLE_IS_FILTERING:
{
state.isFiltering = ! state.isFiltering;
state = state.copyWith();
break;
}
default:
{
state = state.initial();
break;
}
}
yield state;
}
Stream<dynamic> fetchArticles({String filter = ""}) async* {
List<dynamic> list = (this.state.articles.length > 0)
? this.state.articles
: await _repository.getArticles();
if (filter.isNotEmpty) {
for (var article in list) {
if (article is String) {
yield article;
} else if (article.title.contains(filter)) {
yield article;
}
}
} else {
for (var article in list) {
yield article;
}
}
}
}
class HomeState {
bool _isFiltering = false;
List<dynamic> _articles = List<dynamic>();
bool get isFiltering => _isFiltering;
void set isFiltering(bool isFiltering) {
this._isFiltering = isFiltering;
}
List<dynamic> get articles => _articles;
void set articles(List<dynamic> list) {
this._articles = list;
}
HomeState initial() {
HomeState state = HomeState();
state.isFiltering = false;
state.articles = List<dynamic>();
return state;
}
HomeState copyWith({ bool isFiltering, List<dynamic> articles }) {
HomeState state = HomeState();
state.isFiltering = isFiltering != null? isFiltering: this._isFiltering;
state.articles = articles!=null && articles.length > 0? articles: this._articles;
return state;
}
}
This is my repository class returning dummy data.
class Repository {
Future<List<dynamic>> getArticles() async {
List<dynamic> list = List<dynamic>();
list.add("A");
Article article1 = Article();
article1.id = 1;
article1.title = "A start is born";
list.add(article1);
Article article2 = Article();
article2.id = 2;
article2.title = "Asking for help";
list.add(article2);
Article article3 = Article();
article3.id = 3;
article3.title = "Angel is comming";
list.add(article3);
list.add("B");
Article article4 = Article();
article4.id = 4;
article4.title = "Baby Boss";
list.add(article4);
Article article5 = Article();
article5.id = 5;
article5.title = "Beginner guide to Staying at Home";
list.add(article5);
list.add("C");
Article article6 = Article();
article6.id = 6;
article6.title = "Care each other";
list.add(article6);
Article article7 = Article();
article7.id = 7;
article7.title = "Controlling the world";
list.add(article7);
Article article8 = Article();
article8.id = 8;
article8.title = "Chasing the dream";
list.add(article8);
return list;
}
}
This is my HomePage widget
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
IconData _searchIcon = Icons.search;
Widget _appBarTitle;
HomeBloc _homeBloc;
#override
void initState() {
super.initState();
this._homeBloc = BlocProvider.of(context);
WidgetsBinding.instance.addPostFrameCallback((_) => this.fetchArticles());
WidgetsBinding.instance
.addPostFrameCallback((_) => this.buildAppBarTitle());
}
#override
Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text("Home"),),
body: Container(
child: buildListView(context, state),
),
);
},
);
}
#override
void dispose() {
super.dispose();
}
void buildAppBarTitle() {
this.setState(() {
if (_searchIcon == Icons.search) {
this._appBarTitle = Text("Home");
} else {
this._appBarTitle = TextField(
onChanged: (String inputValue) {
debugPrint("Search term has changed $inputValue");
//homeBloc.fetchArticles(filter: inputValue);
},
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
hintText: "Search",
),
);
}
});
}
Widget buildAppBarSearchIcon() {
return IconButton(
icon: Icon(
_searchIcon,
color: Colors.white,
),
onPressed: () {
if (this._searchIcon == Icons.search) {
//display the search text field and close icons
this.setState(() {
this._searchIcon = Icons.close;
this.buildAppBarTitle();
//homeBloc.toggleFiltering();
});
} else {
this.fetchArticles();
this.setState(() {
this._searchIcon = Icons.search;
this.buildAppBarTitle();
//homeBloc.toggleFiltering();
});
}
});
}
Widget buildListView(
BuildContext context, HomeState state) {
if (state.articles.length > 0) {
var listView = ListView.builder(
itemCount: state.articles.length,
itemBuilder: (context, index) {
var item = state.articles[index];
if (item is String) {
return buildListFirstInitialView(item);
}
Article article = item as Article;
return buildListArticleView(article);
});
return listView;
} else {
return Center(
child: Text("No resources found."),
);
}
}
Widget buildListFirstInitialView(String initial) {
return ListTile(
title: Text(initial),
);
}
Widget buildListArticleView(Article article) {
return ListTile(
title: Text(article.title),
);
}
Widget buildBottomNavigationBar() {
return BottomNavigationBar(
currentIndex: 0,
onTap: (int position) {},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
]);
}
void fetchArticles({String filter = ""}) {
HomeEvent event = HomeEvent();
event.event = HomeEvent.FETCH_ARTICLES;
_homeBloc.add(event);
}
}
As you can see this is my HomePage widget is doing. It will fetch the articles after the widget is built. Then the list view will be updated with the dummy data.
My main.dart file.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => HomeBloc(),
child: HomePage(),
),
);
}
}
When I run my app, it is not updating the list view with the dummy data. Instead, it is always showing the message for no records found.
Why is it not working?
Bloc will never change If the state didn't change, you might be confused that you assigned a state, but the fact is that Bloc is a Stream, you'll need to yield a state instead of assigning It directly.
Hopefully, you had implemented a copyWith, so you could do It as below:
yield state.copyWith(articles: articles)
For keeping your structure, you could still use:
// state = state.copyWith(articles: articles);
newState = state.copyWith(articles: articles);
...
yield newState;
Because state variable is used by Bloc, you must use another variable to prevent Bloc from comparing the same variable (state == state will always true, so the state never changed.)
your state class is not equatable ... Dart can't tell when the state has changed
You must use:
import 'package:equatable/equatable.dart';
I also don't think you should SET the state property on your bloc class. You should only yield it and let the bloc update it...
please check the docs as I could be wrong
I had the same issue and solved this problem changing:
yield status;
to
yield status.toList();
At the end of my mapEventToState() method.
And probably you had to make for all yield that is passing a List.
If it worked for you let me know
I am trying to load images from an API which may or may not meet certain criteria. If these criteria are successful, I'd like to display the image on screen. It seems that I can use NetworkImage to load this and check the attributes and if those attributes match, I will add an image to my list.
However, I can't quite figure out how to use the NetworkImage with Image.fromMemory (I'm guessing)
This code seems to be getting me most of the way there (but adding a listener after I call load seems suspect).
Future getImage() async {
var url = 'https://myapi.com/a37ni1.jpg';
var image = new NetworkImage(url);
var config = await image.obtainKey(new ImageConfiguration());
var load = image.load(config);
var listener = new ImageStreamListener((ImageInfo info, isSync) async {
print(info.image.width);
print(info.image.height);
if (info.image.width == 80 && info.image.height == 160) {
//skip
} else {
//Convert the NetworkImage to something I can use in an Image widget
}
});
load.addListener(listener);
}
Any ideas what I might be missing here?
here is an example, but I used another url instead of yours
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text("Test")),
body: Container(
alignment: Alignment.center,
child: FutureBuilder<Widget>(
future: getImage(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data;
} else {
return Text('LOADING...');
}
},
),
),
),
);
}
Future<Widget> getImage() async {
final Completer<Widget> completer = Completer();
final url = 'https://picsum.photos/200/300';
final image = NetworkImage(url);
final config = await image.obtainKey(const ImageConfiguration());
final load = image.load(config);
final listener = new ImageStreamListener((ImageInfo info, isSync) async {
print(info.image.width);
print(info.image.height);
if (info.image.width == 80 && info.image.height == 160) {
completer.complete(Container(child: Text('AZAZA')));
} else {
completer.complete(Container(child: Image(image: image)));
}
});
load.addListener(listener);
return completer.future;
}
}
I am trying to implement a Column with a Text:
Column(
children: <Widget>[
Text('data from future function')
],
),
I can't get the data from initState() cause initState() it's only void
If I get data directly from the function
Text(function)
I get
instance of function
The function:
Future<double> calculate(int index) async {
LocData _getUser = await getLoc();
double uLat = _getUser.latitude;
double uLng = _getUser.latitude;
double pLat = parks[data].loc.lat;
double pLng = parks[data].loc.lng;
double dis = await Geolocator()
.distanceBetween(uLat , uLng, uLng , pLat );
return dis ;
}
Any idea what can i do to get this data from the function directly to the text wigdet?
There 2 ways to get data from a future.
Option #1:
(with 2 suboptions)
class MyWidgetState extends State<MyWidget> {
String _someAsyncData;
#override
void initState() {
super.initState();
// opt 1.
aDataFuture.then((val) {
setState(() {
_someAsyncdata = val;
});
});
// opt 2.
_setAsyncData(aDataFuture);
}
void _setAsyncData(Future<String> someFuture) async {
// void + async is considered a "fire and forget" call
// part of opt. 2
_someAsyncData = await someFuture;
// must trigger rebuild with setState
setState((){});
}
Widget build(BuildContext context) {
return _someAsyncData == null ? Container() : Text('$_someAsyncData');
}
}
Option #2
Use FutureBuilder
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _someFuture,
builder: (ctx, snapshot) {
// can also check for snapshot.hasData or snapshot.hasError to
// provide more user feedback eg.
if(snapshot.connectionState == ConnectionState.done)
return Text('${snapshot.data}');
return Text('No data available yet...');
}
);
}
}
Here is the full working code.
class _InfoPageState extends State<InfoPage> {
String _text = "";
#override
void initState() {
super.initState();
calculate(10).then((value) {
setState(() {
_text = value.toString();
});
});
}
Future<double> calculate(int index) async {
LocData _getUser = await getLoc();
double uLat = _getUser.latitude;
double uLng = _getUser.latitude;
double pLat = parks[data].loc.lat;
double pLng = parks[data].loc.lng;
double dis = await Geolocator().distanceBetween(uLat, userLng, uLng, pLat);
return dis;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(children: <Widget>[Text(_text)]),
);
}
}