I just started learning Flutter. And I faced one big problem which is bad scrolling in complex list. Let's say we have 5 different item type in our ListView and some item type must display images and it's infinite scroll. I read a lot articles and posts about ListView for Flutter and all the things I've seen are simple list with text. How can I make smooth scroll?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
RandomWordsState createState() => new RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(
items.length, items.length + 10); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 1), () {
return List.generate(to - from, (i) => i + from);
});
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text(""),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
if (index % 5 == 0) {
return Image.network(
"http://sanctum-inle-resort.com/wp-content/uploads/2015/11/Sanctum_Inl_Resort_Myanmar_Flowers_Frangipani.jpg",
height: 200.0,
);
} else if (index.isOdd) {
return Container(
padding: EdgeInsets.all(16.0),
child: ListTile(
leading: Icon(Icons.person),
title: Text('This is title'),
),
);
} else {
return ListTile(title: new Text("Number $index"));
}
}
},
controller: _scrollController,
),
);
}
}
I think the issue is in the fact that you request the data async when the user is already to the end of the list and has no more items and then it's performing a fake request which loads the data but this takes a sec so that's why it's studdering
_scrollController.position.maxScrollExtent
This gets the end of the scroll list so it starts loading at the point the user is at the end of the list you could change this value with an offset so it starts loading earlier
To test this you could try changing:
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
To:
if (_scrollController.position.pixels ==
(_scrollController.position.maxScrollExtent - 50)) {
And
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 1), () {
return List.generate(to - from, (i) => i + from);
});
}
To:
List<int> fakeRequest(int from, int to) {
return List.generate(to - from, (i) => i + from);
}
Related
Hello Everyone I want to show Admob ads in my flutter pageview after a 5-page swipe and on the 6th page I want a full-page banner ad, if I swipe this then I can go on the 7th page of the news.
I have implemented but I am unable to get full-page banner ads, it shows 312x100 pixels size ads only.
Here is my full code.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'Helper/ad_helper.dart';
import 'Models/news.dart';
import 'package:http/http.dart' as http;
class Tedd extends StatefulWidget {
#override
_TeddState createState() => _TeddState();
}
class _TeddState extends State<Tedd> {
List<NewsModel> _newsList = [];
bool isLoading = true;
late BannerAd _bannerAd;
bool _isBannerAdReady = false;
int currentPage = 1;
bool hasReachedEnd = false;
PageController _pageController = PageController(initialPage: 0);
_getAllNews(currentPage) async {
var articles = await http.get(Uri.parse(
"https://pkbhai.com/myprojects/kids-stories/api/all-stories?page=${currentPage}"));
var result = json.decode(articles.body);
var newDataLength = result['data'].length;
if (newDataLength == 0) {
setState(() {
hasReachedEnd = true;
});
}
result['data'].forEach((data) {
var news = NewsModel();
news.id = data["id"];
news.articleTitle = data["name"];
news.articleDetails = data["details"];
if (mounted) {
setState(() {
_newsList.add(news);
});
}
});
setState(() {
isLoading = true;
});
}
void handleNext() {
_pageController.addListener(() async {
if (_pageController.page?.toInt() == _newsList.length - 1) {
setState(() {
currentPage += 1;
});
_getAllNews(currentPage);
}
});
}
#override
void initState() {
_bannerAd = BannerAd(
adUnitId: AdHelper.bannerAdUnitId,
request: AdRequest(),
size: AdSize.banner,
listener: BannerAdListener(
onAdLoaded: (_) {
setState(() {
_isBannerAdReady = true;
});
},
onAdFailedToLoad: (ad, err) {
print('Failed to load a banner ad: ${err.message}');
_isBannerAdReady = false;
ad.dispose();
},
),
);
_bannerAd.load();
_getAllNews(currentPage);
handleNext();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _newsList.length > 0
? PageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
itemCount: _newsList.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _newsList.length && hasReachedEnd) {
return Container(
color: Colors.red,
);
}
if (index == _newsList.length && !hasReachedEnd) {
return Center(
child: CircularProgressIndicator(),
);
}
if (index % 5 == 0 && index != 0) {
return Container(
child:
// if (_isBannerAdReady)
Align(
alignment: Alignment.topCenter,
child: Container(
width: _bannerAd.size.width.toDouble(),
height: _bannerAd.size.height.toDouble(), // also tried 1000, but not worked
child: AdWidget(ad: _bannerAd),
),
),
);
}
return Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.blue.shade400,
height: MediaQuery.of(context).size.height,
child: PageView(
reverse: true,
children: [
Text(
_newsList[index].articleDetails!,
maxLines: 4,
),
Text(_newsList[index].articleTitle!),
],
),
),
),
);
},
)
: Center(child: CircularProgressIndicator()),
);
}
}
I tried to do give a command to the flutter application using voice commands and in the application there was a text to voice function. so when i tried to give read command using voice there was a repeat on that function. how to avoid the repeat.
I have tried to terminate the Listening function after getting one input but it dosen't work.
Tried to terminate the Listening function.
but same error
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:newsapp/screens/News_Screen.dart';
import 'package:newsapp/utils/app_colors.dart';
import 'package:newsapp/utils/assets_constants.dart';
import 'package:webfeed/webfeed.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_speech/flutter_speech.dart';
const languages = const [
// const Language('Francais', 'fr_FR'),
const Language('English', 'en_US'),
const Language('Pусский', 'ru_RU'),
const Language('Italiano', 'it_IT'),
const Language('Español', 'es_ES'),
];
class Language {
final String name;
final String code;
const Language(this.name, this.code);
}
class BBCNews extends StatefulWidget {
#override
_BBCNewsState createState() => _BBCNewsState();
}
class _BBCNewsState extends State<BBCNews> {
late SpeechRecognition _speech;
FlutterTts flutterTts = FlutterTts();
bool _speechRecognitionAvailable = false;
bool _isListening = false;
String transcription = '';
String _currentLocale = 'en_US';
//Language selectedLang = languages.first;
#override
initState() {
super.initState();
activateSpeechRecognizer();
}
void activateSpeechRecognizer() {
//print('_MyAppState.activateSpeechRecognizer... ');
_speech = SpeechRecognition();
_speech.setAvailabilityHandler(onSpeechAvailability);
_speech.setRecognitionStartedHandler(onRecognitionStarted);
_speech.setRecognitionResultHandler(onRecognitionResult);
_speech.setRecognitionCompleteHandler(onRecognitionComplete);
_speech.setErrorHandler(errorHandler);
_speech.activate('en_US').then((res) {
setState(() => _speechRecognitionAvailable = res);
});
}
var client = http.Client();
Future myDevBlog() async {
var response = await client
.get(Uri.parse('http://feeds.bbci.co.uk/news/world/rss.xml'));
var channel = RssFeed.parse(response.body);
final item = channel.items;
return item;
}
Future<void> openFeed(String url) async {
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: true,
forceWebView: false,
);
return;
}
}
rightIcon() {
return const Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
size: 30,
);
}
#override
Widget build(BuildContext context) {
var currentIndex;
return Scaffold(
appBar: AppBar(
backgroundColor: AppColors.primaryColor,
//title: const Text('VoiceNews'),
title: Image.asset(
AssetConstants.iconPath,
width: 150,
),
elevation: 10.0,
actions: [
IconButton(
onPressed: _speechRecognitionAvailable && !_isListening
? () => start()
: null,
icon: const Icon(Icons.play_arrow_rounded)),
IconButton(
onPressed: _isListening ? () => stop() : null,
icon: const Icon(Icons.stop_circle))
],
),
body: StreamBuilder(
stream: myDevBlog().asStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, int index) {
if (transcription.toLowerCase() == "play") {
// _isListening = false;
//stop();
print(transcription);
speak(snapshot.data[1].title);
var currentIndex = 0;
} else if (transcription.toLowerCase() == "next") {
// _isListening = false;
speak(snapshot.data[2].title);
print(transcription);
} else if (transcription.toLowerCase() == "all") {
// _isListening = false;
errorHandler();
//speak("Wrong Input");
//cancel();
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text(snapshot.data[index].title),
subtitle:
Text(snapshot.data[index].description),
//onTap: () =>
speak(snapshot.data[index].title),
// onTap: () =>
openFeed(snapshot.data[index].link),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
newsScreen(snapshot.data[index].link),
// Pass the arguments as part of the
RouteSettings. The
// DetailScreen reads the arguments
from these settings.
// settings: RouteSettings(
// arguments: todos[index],
// ),
),
);
print(snapshot.data[index].link);
},
onLongPress: () =>
speak(snapshot.data[index].description),
leading: Text((1 + index).toString()),
trailing: rightIcon(),
),
),
);
},
);
} else if (snapshot.hasError ||
snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text(snapshot.error.toString()),
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
// List<CheckedPopupMenuItem<Language>> get
_buildLanguagesWidgets => languages
void start() => _speech.activate(_currentLocale).then((_) {
return _speech.listen().then((result) {
print('_MyAppState.start => result $result');
setState(() {
_isListening = result;
});
});
});
void cancel() =>
_speech.cancel().then((_) => setState(() => _isListening =
false));
void stop() => _speech.stop().then((_) {
setState(() => _isListening = false);
});
void onSpeechAvailability(bool result) =>
setState(() => _speechRecognitionAvailable = result);
// void onCurrentLocale(String locale) {
// // print('_MyAppState.onCurrentLocale... $locale');
// setState(
// () => _currentLocale = languages.firstWhere((l) =>
l.code == locale));
// }
void onRecognitionStarted() {
setState(() => _isListening = true);
}
void onRecognitionResult(String text) {
// print('_MyAppState.onRecognitionResult... $text');
setState(() => transcription = text);
}
void onRecognitionComplete(String text) {
//print('_MyAppState.onRecognitionComplete... $text');
setState(() => _isListening = false);
}
void errorHandler() => activateSpeechRecognizer();
void speak(String text) async {
await flutterTts.speak(text);
await flutterTts.setLanguage("en-US");
await flutterTts.setPitch(1);
await flutterTts.setSpeechRate(0.6);
}
}
I what to refresh home page so I create widget called RefreshWidget look like this
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RefreshWidget extends StatefulWidget {
// final GlobalKey<RefreshIndicatorState> keyRefresh;
final Widget child;
final Future Function() onRefresh;
const RefreshWidget({
Key? key,
// required this.keyRefresh,
required this.child,
required this.onRefresh
}) : super(key: key);
#override
_RefreshWidgetState createState() => _RefreshWidgetState();
}
class _RefreshWidgetState extends State<RefreshWidget> {
#override
Widget build(BuildContext context) =>
Platform.isAndroid ? buildAndroidList() : buildIOSList();
Widget buildAndroidList() => RefreshIndicator(
// key: widget.keyRefresh,
onRefresh: widget.onRefresh,
child: widget.child,
);
Widget buildIOSList() => CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh),
SliverToBoxAdapter(child: widget.child),
],
);
}
I use it in home and it work just fine like this in side body after check the loading
RefreshWidget(
onRefresh:loadPost,
child: StreamBuilder(
stream: homePosts,
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
int len = snapshot.data?.docs.length??0;
for(int i = 0 ; i < len ; i++){
_isloaded.add(false);
userData.add('');
}
for(int i = 0 ; i < len ; i++){
if(!_isloaded[i]) {
getData(snapshot.data!.docs[i].data()['uid'], i);
}
}
if (snapshot.data == null) {
return Center(
child: Container(
child: Text(
"No posts yet!",
style: TextStyle(
color: Palette.textColor,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
);
}
return PageView.builder( ...);
loadPost
Future loadPost() async{
getTheData();
setState(() {
homePosts = FirebaseFirestore.instance.collection('posts').orderBy("datePublished", descending: true).where('uid', whereIn: theUserData['following']).snapshots();
});
}
getTheData
/* get data method */
getTheData() async {
try {
if ( uid!= null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
/*end*/
if (userSnap.data() != null) {
theUserData = userSnap.data()!;
theUserData['following'].add(uid);
setState(() {
_isTheUserLoaded = true;
homePosts = FirebaseFirestore.instance.collection('posts').orderBy("datePublished", descending: true).where('uid', whereIn: theUserData['following']).snapshots();
});
} else
Navigator.of(context).popAndPushNamed('/Signup_Login');
}
} catch (e) {
showSnackBar(context, e.toString());
}
}
The problem
The post save uid. And I retrieve in the following code (copied from the first code I showed):
int len = snapshot.data?.docs.length??0;
for(int i = 0 ; i < len ; i++){
_isloaded.add(false);
userData.add('');
}
for(int i = 0 ; i < len ; i++){
if(!_isloaded[i]) {
getData(snapshot.data!.docs[i].data()['uid'], i);
}
}
When I unfollow someone userData that is a list of user info that posted post that will show in home, is not updated. There for the Home page will show the post with wrong user.
You can find the fill code here on Github: https://github.com/ShathaAldosari01/gp1_7_2022/blob/master/lib/screen/home/TimeLine/home_page.dart
I am using this multi-select flutter package. It already has contents to view chips, but I want to load contents from API to do that instead.
I already added a fetchData() function to download the data from the API. Now, how do I get the JSON data into the chips multi-select?
Here is my code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:multiselect_formfield/multiselect_formfield.dart';
import 'package:multi_select_flutter/multi_select_flutter.dart';
import 'package:http/http.dart' as http;
import 'Includes/APILinks.dart';
void main() => runApp(Sample());
class Sample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _myActivities;
String _myActivitiesResult;
final formKey = new GlobalKey<FormState>();
#override
void initState() {
super.initState();
_myActivities = [];
this.fetchData();
}
_saveForm() {
var form = formKey.currentState;
if (form.validate()) {
form.save();
setState(() {
_myActivitiesResult = _myActivities.toString();
});
}
}
fetchData() async{
var url = CategorySection;
var response = await http.get(url);
if(response.statusCode == 200){
var items = json.decode(response.body);
print(items);
setState(() {
_myActivities = items;
});
} else {
setState(() {
_myActivities = [];
});
}
}
#override
Widget build(BuildContext context) {
final double maxWidth = MediaQuery.of(context).size.width;
final double maxHeight = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
title: Text('MultiSelect Formfield Example'),
),
body: Center(
child: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: maxHeight/2,
width: maxWidth,
padding: EdgeInsets.all(16),
child: getListView(),
),
Container(
padding: EdgeInsets.all(8),
child: RaisedButton(
child: Text('Save'),
onPressed: _saveForm,
),
),
Container(
padding: EdgeInsets.all(16),
child: Text(_myActivitiesResult),
)
],
),
),
),
);
}
Widget getListView() {
return ListView.builder(
itemCount: _myActivities.length,
itemBuilder: (context, index){
return cardView(_myActivities[index]);
},
);
}
Widget cardView(item) {
var fullName = item;
return MultiSelectDialogField(
items: _myActivities,
title: Text("Animals"),
selectedColor: Colors.blue,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.all(Radius.circular(40)),
border: Border.all(
color: Colors.blue,
width: 2,
),
),
buttonIcon: Icon(
Icons.pets,
color: Colors.blue,
),
buttonText: Text(
"Favorite Animals",
style: TextStyle(
color: Colors.blue[800],
fontSize: 16,
),
),
onConfirm: (results) {
_myActivities = results;
},
);
}
}
You can use a Future function with the return type that you want, in that case it will be Future<List<MultiSelectItem>>, then you would use async and await, it will be something like that:
Future<List<Item>> fetchItems(BuildContext context) async {
http.Response response = await http.get(
Uri.parse('your link for ur api'),
//you can ignore that part
headers: <String, String>{
'Authorization':
'Token ${Provider.of<Token>(context, listen: false).token}'
});
//here you turn the json to a <String, dynamic> map
var data = jsonDecode(response.body);
List<Item> result = [];
for (var item in data) {
//use your factory if you wanna to parse the data the way you want it
result.add(Item.fromJson(item));
}
//that should be your disered list
return result;
}
The FilterChip class provides a multiple select chip. Using FutureBuilder we can fetch future data and build the chips list. We can also call the API first, and then map the results with the filter chips.
Here is an example of a FilterChip, where chips are populated from an API using FutureBuilder:
import 'dart:convert';
import 'package:filterchip_sample/album.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FilterChip Sample',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'FilterChip Sample'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool favorite = false;
final List<int> _filters = <int>[];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
FutureBuilder<List<Album>>(
future: _fetchAlbums(),
builder:
(BuildContext context, AsyncSnapshot<List<Album>> snapshot) {
Widget result;
if (snapshot.hasData) {
result = Wrap(
spacing: 5.0,
children: snapshot.data!.map((Album album) {
return FilterChip(
label: Text(album.title),
selected: _filters.contains(album.id),
onSelected: (bool value) {
setState(() {
if (value) {
if (!_filters.contains(album.id)) {
_filters.add(album.id);
}
} else {
_filters.removeWhere((int id) {
return id == album.id;
});
}
});
},
);
}).toList(),
);
} else if (snapshot.hasError) {
result = Text('Error: ${snapshot.error}');
} else {
result = const Text('Awaiting result...');
}
return result;
},
),
],
),
),
);
}
Future<List<Album>>? _fetchAlbums() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/'));
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
var albums = List<Album>.from(json.map((i) => Album.fromJson(i)));
return albums;
} else {
throw Exception('Failed to get albums');
}
}
}
I'm creating a calendar screen by using TableCalender and Cloud firestore.
I want to set _buildEventList() after assigning a value to selectedEvent at the place where selectedEvent is set, but because _buildEventList() is called first, it will be empty .
But, after i set the value I'm calling setState(){}. Why the screen won't be updated?
final _firestore = Firestore.instance;
FirebaseUser loggedInUser;
// Example holidays
final Map<DateTime, List> _holidays = {
DateTime(2019, 1, 1): ['New Year\'s Day'],
DateTime(2019, 1, 6): ['Epiphany'],
DateTime(2019, 2, 14): ['Valentine\'s Day'],
DateTime(2019, 4, 21): ['Easter Sunday'],
DateTime(2019, 4, 22): ['Easter Monday'],
};
class CalenderScreen extends StatefulWidget {
#override
_CalenderScreenState createState() => _CalenderScreenState();
}
class _CalenderScreenState extends State<CalenderScreen>
with TickerProviderStateMixin {
DateTime _selectedDay;
Map<DateTime, List> _events = {};
Map<DateTime, List> _visibleEvents;
Map<DateTime, List> _visibleHolidays;
List _selectedEvents;
AnimationController _controller;
final _auth = FirebaseAuth.instance;
Widget streamBuilder;
Widget buildEvents;
#override
void initState() {
print("calender");
super.initState();
getCurrentUser();
_selectedDay = DateTime.now();
_selectedEvents = _events[_selectedDay] ?? [];
_visibleEvents = _events;
_visibleHolidays = _holidays;
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_controller.forward();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
setStreamBuilder();
}
} catch (e) {
print(e);
}
}
void setStreamBuilder() {
setState(() {
streamBuilder = StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.document(loggedInUser.uid)
.collection('history')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null)
return Center(child: CircularProgressIndicator());
final historys = snapshot.data.documents;
_events = {};
for (var history in historys) {
DateTime timeStamp = history.data['date'].toDate();
DateTime currentDate =
DateTime(timeStamp.year, timeStamp.month, timeStamp.day);
if (_events.containsKey(currentDate)) {
_events[currentDate].add(history);
} else {
_events[currentDate] = [history];
}
}
print(_events);
DateTime now = DateTime.now();
_selectedDay = DateTime(now.year, now.month, now.day);
//here i set the _selectedEvents
_selectedEvents = _events[_selectedDay] ?? [];
_visibleEvents = _events;
return _buildTableCalendar();
},
);
});
}
void _onDaySelected(DateTime day, List events) {
setState(() {
_selectedDay = day;
_selectedEvents = events;
});
}
void _onVisibleDaysChanged(
DateTime first, DateTime last, CalendarFormat format) {
setState(() {
_visibleEvents = Map.fromEntries(
_events.entries.where(
(entry) =>
entry.key.isAfter(first.subtract(const Duration(days: 1))) &&
entry.key.isBefore(last.add(const Duration(days: 1))),
),
);
_visibleHolidays = Map.fromEntries(
_holidays.entries.where(
(entry) =>
entry.key.isAfter(first.subtract(const Duration(days: 1))) &&
entry.key.isBefore(last.add(const Duration(days: 1))),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF232D3D),
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
streamBuilder != null
? streamBuilder
: Center(
child: CircularProgressIndicator(),
),
const SizedBox(height: 8.0),
_buildEventList()
],
),
floatingActionButton: FloatingPenButton(),
);
}
Widget _buildTableCalendar() {
return TableCalendar(
~~
);
}
Widget _buildEventList() {
print("bulid event");
return Expanded(
child: ListView(
children: _selectedEvents
.map(
(event) => HistoryCard(
history: HistoryData(
~~
),
)
.toList(),
),
);
}
}
This has been viewed enough that we are all experiencing/searching the same thing. Basically the initial load does not update the state when the streambuilder first loads. If you click and select a day, then it seems to load fine and populate with the events. If anyone can advise why it doesn't rebuild the widget when the stream gets populated by default that would be appreciated.