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
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'm creating a quiz app using Flutter & Firebase. I've fetched questions & options which don't contain images using Firebase, but I'm not able to fetch questions that contain an image in them.
This is a screenshot of the question, which doesn't have an image, I've fetched in the app:
Question with image is like shown below in excel format:
All these data except images are fetched from JSON file imported in Realtime Database in Firebase.
Below is the code where questions are fetched from Realtime database in Firebase:
import 'package:http/http.dart' as http;
import './question_model.dart';
import 'dart:convert';
import '../test.dart';
class DBconnect {
final url = Uri.parse(
'https://rto1-798fc-default-rtdb.firebaseio.com/questions.json');
Future<List<Question>> fetchQuestions() async {
return http.get(url).then((response) {
var data = json.decode(response.body) as Map<String, dynamic>;
List<Question> newQuestions = [];
data.forEach((key, value) {
var newQuestion = Question(
id: key,
title: value['title'],
options: Map.castFrom(value['options']),
);
newQuestions.add(newQuestion);
newQuestions.shuffle();
});
return newQuestions;
});
}
}
Below is the question model:
class Question {
final String id;
final String title;
final Map<String, bool> options;
Question({
required this.id,
required this.title,
required this.options,
});
#override
String toString() {
return 'Question(id: $id, title: $title, options: $options)';
}
}
Below is the question widget:
import 'package:flutter/material.dart';
import '../constants.dart';
class QuestionWidget extends StatelessWidget {
const QuestionWidget(
{Key? key,
required this.question,
required this.indexAction,
required this.totalQuestions})
: super(key: key);
final String question;
final int indexAction;
final int totalQuestions;
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.centerLeft,
child: Text(
'Question ${indexAction + 1}/20: $question',
style: const TextStyle(
fontSize: 24.0,
color: neutral,
),
),
);
}
}
And this is the main page:
import 'package:flutter/material.dart';
import '../constants.dart';
import '../models/question_model.dart';
import '../widgets/question_widget.dart';
import '../widgets/next_button.dart';
import '../widgets/option_card.dart';
import '../widgets/result_box.dart';
import '../models/db_connect.dart';
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
var db = DBconnect();
late Future _questions;
Future<List<Question>> getData() async {
return db.fetchQuestions();
}
#override
void initState() {
_questions = getData();
super.initState();
}
int index = 0;
int score = 0;
bool isPressed = false;
bool isAlreadySelected = false;
void nextQuestion(int questionLength) {
if (index == 19 || score == 12) {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => ResultBox(
result: score,
questionLength: questionLength,
));
} else {
if (isPressed) {
setState(() {
index++;
isPressed = false;
isAlreadySelected = false;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Please select any option'),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.symmetric(vertical: 20.0),
));
}
}
}
void checkAnswerAndUpdate(bool value) {
if (isAlreadySelected) {
return;
} else {
if (value == true) {
score++;
setState(() {
isPressed = true;
isAlreadySelected = false;
});
} else if (value == false) {
setState(() {
isPressed = true;
isAlreadySelected = false;
});
}
}
}
void startOver() {
setState(() {
Text('You have already attempted the LL Test');
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _questions as Future<List<Question>>,
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text('${snapshot.error}'),
);
} else if (snapshot.hasData) {
var extractedData = snapshot.data as List<Question>;
return Scaffold(
backgroundColor: background,
appBar: AppBar(
title: const Text('LL Test'),
backgroundColor: background,
shadowColor: Colors.transparent,
actions: [
Padding(
padding: const EdgeInsets.all(18.0),
child: Text(
'Score: $score',
style: TextStyle(fontSize: 18.0),
),
)
],
),
body: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
QuestionWidget(
question: extractedData[index].title,
indexAction: index,
totalQuestions: extractedData.length,
),
const Divider(
color: neutral,
),
const SizedBox(height: 25.0),
for (int i = 0;
i < extractedData[index].options.length;
i++)
GestureDetector(
onTap: () => checkAnswerAndUpdate(
extractedData[index].options.values.toList()[i]),
child: OptionCard(
option: extractedData[index].options.keys.toList()[i],
color: isPressed
? extractedData[index]
.options
.values
.toList()[i] ==
true
? correct
: incorrect
: neutral,
),
),
],
),
),
floatingActionButton: GestureDetector(
onTap: () => nextQuestion(extractedData.length),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: NextButton(),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
);
}
} else {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20.0),
Text(
'Please Wait While Questions Are Loading..',
style: TextStyle(
backgroundColor: Color.fromARGB(255, 4, 82, 6),
color: neutral),
),
],
),
);
}
return const Center(
child: Text('NoData'),
);
},
);
}
}
I'm trying to implement Flutter InApp purchases on Consumables but I keep getting the following - error you already own this item when I try to buy again.
I want the user to buy over and over again.
This is happening on android.
I'm using in_app_purchase: ^0.3.4+5
I used the code in official docs for the plugin -
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase/store_kit_wrappers.dart';
import 'consumable_store.dart';
void main() {
// For play billing library 2.0 on Android, it is mandatory to call
// [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
// as part of initializing the app.
InAppPurchaseConnection.enablePendingPurchases();
runApp(MyApp());
}
// Original code link: https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/example/lib/main.dart
const bool kAutoConsume = true;
const String _kConsumableId = '';
const String _kSubscriptionId = '';
const List<String> _kProductIds = <String>[
_kConsumableId,
'noadforfifteendays',
_kSubscriptionId
];
// TODO: Please Add your android product ID here
const List<String> _kAndroidProductIds = <String>[
''
];
//Example
//const List<String> _kAndroidProductIds = <String>[
// 'ADD_YOUR_ANDROID_PRODUCT_ID_1',
// 'ADD_YOUR_ANDROID_PRODUCT_ID_2',
// 'ADD_YOUR_ANDROID_PRODUCT_ID_3'
//];
// TODO: Please Add your iOS product ID here
const List<String> _kiOSProductIds = <String>[
''
];
//Example
//const List<String> _kiOSProductIds = <String>[
// 'ADD_YOUR_IOS_PRODUCT_ID_1',
// 'ADD_YOUR_IOS_PRODUCT_ID_2',
// 'ADD_YOUR_IOS_PRODUCT_ID_3'
//];
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
StreamSubscription<List<PurchaseDetails>> _subscription;
List<String> _notFoundIds = [];
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
List<String> _consumables = [];
bool _isAvailable = false;
bool _purchasePending = false;
bool _loading = true;
String _queryProductError;
#override
void initState() {
DateTime currentDate = DateTime.now();
DateTime noADDate;
var fiftyDaysFromNow = currentDate.add(new Duration(days: 50));
print('${fiftyDaysFromNow.month} - ${fiftyDaysFromNow.day} - ${fiftyDaysFromNow.year} ${fiftyDaysFromNow.hour}:${fiftyDaysFromNow.minute}');
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
initStoreInfo();
super.initState();
}
Future<void> initStoreInfo() async {
final bool isAvailable = await _connection.isAvailable();
if (!isAvailable) {
setState(() {
_isAvailable = isAvailable;
_products = [];
_purchases = [];
_notFoundIds = [];
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
ProductDetailsResponse productDetailResponse =
await _connection.queryProductDetails(Platform.isIOS ? _kiOSProductIds.toSet() : _kAndroidProductIds.toSet());//_kProductIds.toSet());
if (productDetailResponse.error != null) {
setState(() {
_queryProductError = productDetailResponse.error.message;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
if (productDetailResponse.productDetails.isEmpty) {
setState(() {
_queryProductError = null;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
final QueryPurchaseDetailsResponse purchaseResponse =
await _connection.queryPastPurchases();
if (purchaseResponse.error != null) {
// handle query past purchase error..
}
final List<PurchaseDetails> verifiedPurchases = [];
for (PurchaseDetails purchase in purchaseResponse.pastPurchases) {
if (await _verifyPurchase(purchase)) {
verifiedPurchases.add(purchase);
}
}
List<String> consumables = await ConsumableStore.load();
setState(() {
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = verifiedPurchases;
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = consumables;
_purchasePending = false;
_loading = false;
});
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
List<Widget> stack = [];
if (_queryProductError == null) {
stack.add(
ListView(
children: [
_buildConnectionCheckTile(),
_buildProductList(),
_buildConsumableBox(),
],
),
);
} else {
stack.add(Center(
child: Text(_queryProductError),
));
}
if (_purchasePending) {
stack.add(
Stack(
children: [
Opacity(
opacity: 0.3,
child: const ModalBarrier(dismissible: false, color: Colors.grey),
),
Center(
child: CircularProgressIndicator(),
),
],
),
);
}
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('IAP Example'),
),
body: Stack(
children: stack,
),
),
);
}
Card _buildConnectionCheckTile() {
if (_loading) {
return Card(child: ListTile(title: const Text('Trying to connect...')));
}
final Widget storeHeader = ListTile(
leading: Icon(_isAvailable ? Icons.check : Icons.block,
color: _isAvailable ? Colors.green : ThemeData.light().errorColor),
title: Text(
'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'),
);
final List<Widget> children = <Widget>[storeHeader];
if (!_isAvailable) {
children.addAll([
Divider(),
ListTile(
title: Text('Not connected',
style: TextStyle(color: ThemeData.light().errorColor)),
subtitle: const Text(
'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'),
),
]);
}
return Card(child: Column(children: children));
}
Card _buildProductList() {
if (_loading) {
return Card(
child: (ListTile(
leading: CircularProgressIndicator(),
title: Text('Fetching products...'))));
}
if (!_isAvailable) {
return Card();
}
final ListTile productHeader = ListTile(title: Text('Products for Sale'));
List<ListTile> productList = <ListTile>[];
if (_notFoundIds.isNotEmpty) {
productList.add(ListTile(
title: Text('[${_notFoundIds.join(", ")}] not found',
style: TextStyle(color: ThemeData.light().errorColor)),
subtitle: Text(
'This app needs special configuration to run. Please see example/README.md for instructions.')));
}
// This loading previous purchases code is just a demo. Please do not use this as it is.
// In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it.
// We recommend that you use your own server to verity the purchase data.
Map<String, PurchaseDetails> purchases =
Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
InAppPurchaseConnection.instance.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
}));
productList.addAll(_products.map(
(ProductDetails productDetails) {
PurchaseDetails previousPurchase = purchases[productDetails.id];
return ListTile(
title: Text(
productDetails.title,
),
subtitle: Text(
productDetails.description,
),
trailing: previousPurchase != null
? Icon(Icons.check)
: FlatButton(
child: Text(productDetails.price),
color: Colors.green[800],
textColor: Colors.white,
onPressed: () {
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
applicationUserName: null,
sandboxTesting: false);
if (productDetails.id == _kConsumableId) {
_connection.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: kAutoConsume || Platform.isIOS);
} else {
_connection.buyNonConsumable(
purchaseParam: purchaseParam);
}
},
));
},
));
return Card(
child:
Column(children: <Widget>[productHeader, Divider()] + productList));
}
Card _buildConsumableBox() {
if (_loading) {
return Card(
child: (ListTile(
leading: CircularProgressIndicator(),
title: Text('Fetching consumables...'))));
}
if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) {
return Card();
}
final ListTile consumableHeader =
ListTile(title: Text('Purchased consumables'));
final List<Widget> tokens = _consumables.map((String id) {
return GridTile(
child: IconButton(
icon: Icon(
Icons.stars,
size: 42.0,
color: Colors.orange,
),
splashColor: Colors.yellowAccent,
onPressed: () => consume(id),
),
);
}).toList();
return Card(
child: Column(children: <Widget>[
consumableHeader,
Divider(),
GridView.count(
crossAxisCount: 5,
children: tokens,
shrinkWrap: true,
padding: EdgeInsets.all(16.0),
)
]));
}
Future<void> consume(String id) async {
print('consume id is $id');
await ConsumableStore.consume(id);
final List<String> consumables = await ConsumableStore.load();
setState(() {
_consumables = consumables;
});
}
void showPendingUI() {
setState(() {
_purchasePending = true;
});
}
void deliverProduct(PurchaseDetails purchaseDetails) async {
print('deliverProduct'); // Last
// IMPORTANT!! Always verify a purchase purchase details before delivering the product.
if (purchaseDetails.productID == _kConsumableId) {
await ConsumableStore.save(purchaseDetails.purchaseID);
List<String> consumables = await ConsumableStore.load();
setState(() {
_purchasePending = false;
_consumables = consumables;
});
} else {
setState(() {
_purchases.add(purchaseDetails);
_purchasePending = false;
});
}
}
void handleError(IAPError error) {
setState(() {
_purchasePending = false;
});
}
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
// IMPORTANT!! Always verify a purchase before delivering the product.
// For the purpose of an example, we directly return true.
print('_verifyPurchase');
return Future<bool>.value(true);
}
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
// handle invalid purchase here if _verifyPurchase` failed.
print('_handleInvalidPurchase');
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
print('_listenToPurchaseUpdated');
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
handleError(purchaseDetails.error);
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
return;
}
}
if (Platform.isAndroid) {
if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) {
await InAppPurchaseConnection.instance
.consumePurchase(purchaseDetails);
}
}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
}
How do I solve this?
If you want to test only with non-consumable product, then you can refund the test purchase and use it again later.
Make sure your products are consumable, and also being purchased using the buyConsumable call.
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.
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);
}