How to get async value in Flutter - flutter

I have this function that retrieves data from firestore and returns a list. I and call this function from another function.
the function that retrieves data and returns the list:
Future<List> getNewsOnSearchBar() async{
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
Future<QuerySnapshot> getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
QuerySnapshot val = await getData();
if (val.documents.length > 0) {
print("3");
for (int i = 0; i < val.documents.length; i++) {
newsList.add(val.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
print("4");
return newsList.toList();
}
And I am calling this getNewsOnSearchBar() function from another function that returns a Widget as below:
Widget _showSearchBar(BuildContext context) {
var list = [];
getNewsOnSearchBar().then((value){
print(value); //this will print successfully
list = value; //but this assign doesn't work
});
print(list); //prints an empty list
return GFSearchBar(
// overlaySearchListHeight: 160.0,
searchList: list, //empty
searchQueryBuilder: (query, list) {
return list
.where((item) => item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
overlaySearchListItemBuilder: (item) {
return Container(
padding: const EdgeInsets.all(3),
child: Text(
item,
style: const TextStyle(fontSize: 18),
),
);
},
onItemSelected: (item) {},
);
}
Can someone help me, please?

You are using Future in a wrong way, when you are building a widget that depends on a value from a Future, you need to use FutureBuilder,
So for example your _showSearchBar method -which,I assume, you are calling inside build to show something on the screen- will become:
Widget _showSearchBar(BuildContext context) {
return FutureBuilder(
future: getNewsOnSearchBar(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return GFSearchBar(
// overlaySearchListHeight: 160.0,
searchList: list, //empty
searchQueryBuilder: (query, list) {
return list
.where((item) => item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
overlaySearchListItemBuilder: (item) {
return Container(
padding: const EdgeInsets.all(3),
child: Text(
item,
style: const TextStyle(fontSize: 18),
),
);
},
onItemSelected: (item) {},
);
} else {
return CircularProgressIndicator();
}
}
}
);
}
Learn more about Futures and async/await here:
https://www.youtube.com/watch?v=OTS-ap9_aXc
https://www.youtube.com/watch?v=SmTCmDMi4BY
https://www.youtube.com/watch?v=ek8ZPdWj4Qo

Related

Bloc event not receiving data even after emitting data correctly?

I am having issue that my Blocbuilder is not getting updated even after i am emitting the changes.
Like when i am fetch the data from the firebase by using StreamBuilder, the data is being fetched correctly and i am storing it in the visibleItem list. I have added a search functionality in which i using bloc and whenever my onchanged method gets called i update the state of it and emit the updated result list, but the issue is the blocbuilder isn't getting that state. I have seen all this by adding debuggers in the code, the result is getting emitted correctly. Please take a look at the code.
CateGoryScreen in which i have fetched the data and showing the data
visibleItems =
searchState.results.where((item) => item.isVisible).toList();
so in this line i am seeing wether that item isVisble or not which is being updated in the searcBloc class
Widget build(BuildContext context) {
return StreamBuilder(
stream: categoryBloc.categories,
builder: (context, AsyncSnapshot<List<CategoryModel>> snapshot) {
if (snapshot.hasData) {
_searchBloc = SearchBloc(snapshot.data ?? []);
return _buildScreen();
} else {
return const Center(child: CircularProgressIndicator());
}
});
}
Widget _buildScreen() {
final double appBarHeight =
MediaQuery.of(context).padding.top + kToolbarHeight;
return WillPopScope(
onWillPop: () async {
return _showDiscardWidget(context);
},
child: MultiBlocProvider(
providers: [
BlocProvider<ItemSelectionCubit>(create: (_) => ItemSelectionCubit()),
BlocProvider<SearchBloc>(create: (_) => _searchBloc),
],
child: BlocBuilder<SearchBloc, SearchState>(
builder: (context, searchState) {
final bool shouldDisplayBackgorund =
_searchController.text.isEmpty &&
MediaQuery.of(context).viewInsets.bottom == 0;
visibleItems =
searchState.results.where((item) => item.isVisible).toList();
return BlocBuilder<ItemSelectionCubit, ItemSelectionState>(
builder: (context, itemSelectionState) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: CustomScrollView(
controller: _scrollController,
slivers: [
CustomSliverAppBar(
maxExtent: shouldDisplayBackgorund
? appBarBackgroundHeight
: appBarHeight,
minExtent: appBarHeight,
appBar: _popupAppbar(
appBarHeight, itemSelectionState, context),
background: shouldDisplayBackgorund
? _backgroundAppbar(itemSelectionState, context)
: null,
),
SliverList(
delegate: SliverChildListDelegate(
[
visibleItems.isNotEmpty
? CategoryItemWidget(itemSelectionState,
visibleItems, searchState.query)
: NoResultFoundWidget(
resultQuery: searchState.query ?? ""),
],
),
),
],
),
),
);
},
);
},
),
),
);
}
Widget _popupAppbar(double appBarHeight,
ItemSelectionState itemSelectionState, BuildContext context) {
return Container(
height: appBarHeight,
width: double.infinity,
decoration: const BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(color: Colors.black26, offset: Offset(1, 0), blurRadius: 5),
]),
child: Align(
alignment: Alignment.bottomCenter,
child: SearchAppBar(
hint: 'Search Category',
onChanged: (query) {
_searchBloc.add(SearchQueryChanged(query: query));
},
onPressedPrefix: () {
Navigator.of(context).pop();
},
onPressedSuffix: () {
BlocProvider.of<ItemSelectionCubit>(context).toggleView();
},
searchController: _searchController,
onPressedSuffixInSearchBar: () {
_searchController.clear();
_searchBloc.add(SearchCleared());
},
suffixIconPath: itemSelectionState.isGrid
? AppAssetsPath.gridIcon
: AppAssetsPath.listIcon,
isExpanding: visibleItems.isEmpty,
),
),
);
}
onChanged: (query) {
_searchBloc.add(SearchQueryChanged(query: query));
},
so in this i am changing my searchquery here
searcBloc class in which my all logic for search is
class SearchBloc extends Bloc<SearchEvent, SearchState> {
final List<CategoryModel> _items;
SearchBloc(this._items)
: super(SearchState(
query: null,
results: _items.map((item) => item..isVisible = true).toList())) {
on<SearchQueryChanged>((event, emit) {
emit(_mapSearchQueryChangedToState(event.query));
});
on<SearchCleared>((event, emit) {
emit(_mapSearchClearedToState());
});
}
void onSearchQueryChanged(String query) {
add(SearchQueryChanged(
query: query,
));
}
void onSearchCleared() {
add(SearchCleared());
}
SearchState _mapSearchQueryChangedToState(String query) {
final results = _items.map((item) {
item.isVisible =
item.displayName!.toLowerCase().contains(query.toLowerCase());
return item;
}).toList();
return SearchState(query: query, results: results);
}
SearchState _mapSearchClearedToState() {
final results = _items.map((item) {
item.isVisible = true;
return item;
}).toList();
return SearchState(query: null, results: results);
}
}
class SearchState {
final String? query;
final List<CategoryModel> results;
SearchState({this.query, required this.results});
}
abstract class SearchEvent {
const SearchEvent();
}
class SearchQueryChanged extends SearchEvent {
final String query;
const SearchQueryChanged({required this.query});
}
class SearchCleared extends SearchEvent {}
and categoryBloc where i am fetching the data is
class CategoryBloc {
final _categoryController = StreamController<List<CategoryModel>>.broadcast();
Stream<List<CategoryModel>> get categories => _categoryController.stream;
Future<void> fetchCategories() async {
try {
await getData<CategoryModel>(
path: 'common', builder: (data) => CategoryModel.fromJson(data))
.then((value) {
_categoryController.sink.add(value);
});
} catch (error) {
print(error);
_categoryController.sink.addError(error);
}
}
void dispose() {
_categoryController.close();
}
}
Future<List<T>> getData<T>(
{required String path,
required T Function(Map<String, dynamic>) builder}) async {
List<T> list = [];
try {
final response = await FirebaseFirestore.instance
.collection(path)
.doc('onboardingCategories')
.get();
final data = response.data();
final List<dynamic> rawData = data!['categories'];
list = rawData.map((item) => builder(item)).toList();
return list;
} on FirebaseException catch (e) {
if (kDebugMode) {
print("Failed with error '${e.code}': ${e.message}");
}
return list;
} catch (e) {
throw Exception(e.toString());
}
}
Before fetching the data i tried using sample data only , like creating the sample class and that time i was able to fetch the data correctly, idk what is happening now i am fetching the data correctly, all the data is getting displayed but when i am using search bar it's not updating. Any help would be appreciated

How to display in dialog variable from API request in Flutter

I want to access the variable totalPresences that I have in my API request where I sum up the values from a map. Then I want to display the variable in my widget inside a dialog. How can I do that? Thanks in advance!
Here is my code
Future<List<Presence>> getPresencesByAthleteId() async {
try {
final response = await http.get(
Uri.parse();
if (response.statusCode == 200) {
Map map = json.decode(response.body);
List<Presence>? presencesList = [];
map.forEach((key, value) {
presencesList.add(Presence(
date: map.entries.first.key, count: map.entries.first.value));
var values = map.values;
var totalPresences = values.reduce((sum, element) => sum + element); //this I want to display it in a text
});
return presencesList.toList();
}
} catch (e) {
logger.e(e.toString());
}
return getPresencesByAthleteId(depId, teamId, id, context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Athlete>>(
...
secondary: IconButton(
icon: const Icon(Icons.history_outlined,
color: Colors.black, size: 25),
onPressed: () {
if (_athlete[i].currentMonthPresences! > 0) {
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
children: [
Column(
FutureBuilder<List<Presence>>(
future: getPresencesByAthleteId(_athlete[i].department!.id, widget._team.teamKey!.teamId, _athlete[i].id, context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
...
}),
);
} else if (snapshot.hasError) {
logger.e('${snapshot.error}');
}
}),
Container(
child:
Row(
children: [
const Text(''), // HERE I WANT TO DISPLAY totalPresences
)
],
),
),
It was easier than I thought I just needed a setState inside my api request like this:
int total=0;
Future<List<Presence>> getPresencesByAthleteId() async {
try {
final response = await http.get(
Uri.parse();
if (response.statusCode == 200) {
Map map = json.decode(response.body);
List<Presence>? presencesList = [];
map.forEach((key, value) {
presencesList.add(Presence(
date: map.entries.first.key, count: map.entries.first.value));
var values = map.values;
var totalPresences = values.reduce((sum, element) => sum + element);
setState(() {
totalPresences = total;
});
});
return presencesList.toList();
}
} catch (e) {
logger.e(e.toString());
}
return getPresencesByAthleteId(depId, teamId, id, context);
}
and then just display in dialog
.
.
const Text($total),

How to update UI in Flutter

I'm reading a list from my local Json file and i'm trying to sort the list by either number or alphabet and update the UI depend on user choice.
I'm able to filter the List but not really sure how to update the UI when a user press a either button so I would be really appreciated if I can get any help or suggestion.
Right now, I just called one function in my FutureBuilder and not sure how to modify it.
class _SawnawkScreenState extends State<SawnawkScreen> {
#override
Widget build(BuildContext context) {
bool isSwitched = false;
return Scaffold(
body: FutureBuilder(
future: SortbyNumber(), // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
var items = data.data as List<SawnAwkModel>;
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}
In order to update the UI, the code that changes the UI must be in a setState({}) function. In your case, try this:
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
setState({
final sorted = await SortbyAlphabet()
//update widget contents with sorted value above
})
}),
Your current code if difficult to update the UI, I suggest storing the ListView.builder items in a variable accessible by the function you want to use to update the UI, and change the contents there, like this:
class _SawnawkScreenState extends State<SawnawkScreen> {
bool isSwitched = false;
List items = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: SortbyNumber(), // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
items.addAll(data.data as List<SawnAwkModel>);
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () async {
setState({
print('sort by alphabet'),
final newItems = await SortbyAlphabet();
items.clear();
items.addAll(newItems);
})
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () async {
setState({
print('sort by number'),
final newItems = await SortbyNumber();
items.clear();
items.addAll(newItems);
})
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}
Please refer to this https://stackoverflow.com/a/70202810/15215450 for example on ValueListenable Builder
Please refer to the below code
final ValueNotifier<List> items = ValueNotifier([]);
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
items.value.clear();
items.value = await SortbyAlphabet();
items.notifyListeners();
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
items.value.clear();
items.value = await SortbyAlphabet();
items.notifyListeners();
}),
],
),
ValueListenableBuilder(
valueListenable: isSwitched,
builder: (context, snapshot, child) {
return ListView.builder(
itemCount: items.value == null ? 0 : items.value.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items.value[index].id!,
);
});
}));
Try this
late Future<dynamic> _future;
#override
void initState() {
_future = getDoctors();
}
class _SawnawkScreenState extends State<SawnawkScreen> {
#override
Widget build(BuildContext context) {
bool isSwitched = false;
return Scaffold(
body: FutureBuilder(
future: _future, // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
var items = data.data as List<SawnAwkModel>;
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
setState(() {. // call setstate to refresh futurebuilder
_future = SortbyAlphabet();
}),
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}

Flutter fetch nested future

I have a method that fetches a PatientLog from SQLite.However, This PatientLog table mapped to an object with a class named PatientLog. Inside this PatientLog class, several other objects such as Speciality, AttendingPhysician, Course, etc. I need to map these PatienLog records to a local object. However, I have to use nested Futures. I need to retrieve the data from this nested Future. Think of Future of Future.
This is my fetch method
Future<List<Future<PatientLog>>> getForms() async {
Database db = await instance.getDatabase;
List<Map<String, dynamic>> forms =
await db.query(_tablePatientLog, orderBy: 'id DESC');
Institute? institute;
AttendingPhysician? attendingPhysician;
Speciality? speciality;
Course? course;
List<Future<PatientLog>> list = forms.map((myMap) async {
int? courseId = myMap['course_id'] as int?;
int? specialityId = myMap['speciality_id'] as int?;
int? attendingId = myMap['attending_id'] as int?;
int? instituteId = myMap['institute_id'] as int?;
if (courseId != null) {
await getCourse(courseId).then((value) => course=value);
}
if (attendingId != null) {
await getAttending(attendingId).then((value) => attendingPhysician=value);
}
if (specialityId != null) {
await getSpeciality(specialityId).then((value) => speciality=value);
}
if (instituteId != null) {
await getInstitute(instituteId).then((value) => institute=value);
}
return PatientLog.fromMap(
myMap, institute, course, attendingPhysician, speciality);
}).toList();
return list;
}
I need to display that information on a screen. I get an error type 'List<Future<PatientLog>>' is not a subtype of type 'Future<Object?>?'
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
super.initState();
_refresh();
}
late List<Future<PatientLog>> fromDatabase;
Future<dynamic> _refresh() async {
await _helper.getForms().then((value) async{
setState(() {
fromDatabase = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _helper.getForms(),
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return Center(
child: Text(
"Henüz kaydedilmiş taslak bulunmamaktadır.",
textAlign: TextAlign.center,
style: TEXT_STYLE,
));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Sanırım bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: snapshot.data,
builder: (context,innerSnap) {
return Text(innerSnap.toString());/*CustomListTile(
formData: innerSnap.data[index],
index: index,
routeTo: 1,
isDeletable: true,
);*/
}
);
},
),
),
);
}
return const Center(
child: Text("Nothing")//spinkit,
);
}),
);
}
}

Future builder returning length as null

I am retreiving data from cloud firestore and using Future builder and Listview Builder to display the data. But i am getting null values in the Listview builder i.e displaying the CircularProgressIndicator always.Can't figure out the problem.Any solution will be of great help.
The print(values) function prints out: [9,8] successfully
This is the code i implemented:
Future<List> getassignment() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final name = prefs.getString('orgname') ?? '';
print(name);
var query = FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
query.snapshots().forEach((doc) {
List values = List.from(doc.data()['fields']['class']);
print(values);
return values;
});
}
// void getlist() async{
// await getassignment();
// }
#override
void initState() {
// getlist();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF1976D2),
body: FutureBuilder(
future: getassignment(),
builder: (context,snapshot){
List list = snapshot.data;
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else{
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, position) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute<Null>(
builder: (BuildContext context){
return new SubjectList(
clas: list[position].toString(),
);
}
));
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(list[position].toString(), style: TextStyle(fontSize: 22.0),),
),
),
);
},
),
);
}
},
),
);
}
You are assigning and returning data inside of foreach loop. So that won't return anything.
// try adding await in this line.
var query = await FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
List values = query.snapshots().forEach((doc) => List.from(doc.data()['fields']['class']));
print(values);
return values;
You need to do something like this.