I made a social media application using Flutter Firebase, and like every social media application, I have a stream of posts shared by users on the home screen. At first, I didn't have any problems, but as the number of data increased, I started to have problems especially getting photos. Later I found out that this was because I was getting all the data at once and decided to use Pagination. I have successfully used Pagination and I also started using Cached Network Image to load my photos faster. But I still have such a problem in the flow. When I scroll the screen to the bottom, the data is loaded at the limit I set, in the example my limit is 12, so I have no problem when scrolling down the screen, but when I want to quickly scroll the screen up, it tries to load all the data again, the system is having too much difficulty, I can't load it at the end and the application gives a lost connection error and closes itself.
In my opinion, the same thing should happen when we swipe the screen up, just as the data is loaded piece by piece as much as the limit number we set when swiping down the screen.
Otherwise, this problem that I am experiencing occurs.
Do you know any solution for this?
This is my code for Pagination;
getData() async {
var Ref1 = (widget.post != null)
? _firestore
.collection("users")
.doc(widget.post["profileID"])
.collection("Datas")
.orderBy("uploadTime", descending: true)
.limit(perpage)
: null;
setState(() {
loadingProducts = true;
});
var reponse = await Ref1.get();
listt = reponse.docs;
lastDocument = reponse.docs[reponse.docs.length - 1];
setState(() {
loadingProducts = false;
});
}
getmoreData() async {
if (moreDataAvailable == false) {
return;
}
if (gettingmoreData == true) {
return;
}
setState(() {
gettingmoreData = true;
});
var Ref1 = (widget.post != null)
? _firestore
.collection("users")
.doc(widget.post["profileID"])
.collection("Datas")
.orderBy("uploadTime", descending: true)
.startAfterDocument(lastDocument)
.limit(perpage)
: null;
var reponse = await Ref1.get();
if (reponse.docs.length < perpage) {
moreDataAvailable = false;
}
lastDocument = reponse.docs[reponse.docs.length - 1];
listt.addAll(reponse.docs);
setState(() {});
setState(() {
gettingmoreData = false;
});
}
And this is my Builder;
GridView.builder(
controller: scrollController,
physics: ScrollPhysics(),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemCount: listt.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () =>
navigateToDetail(listt[index]),
child: Hero(
tag: (listt[index]["foto"] != null)
? NetworkImage(
listt[index]["foto"])
: AssetImage(
"assets/images/n_image.jpg"),
child: Container(
child: Column(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
Container(
height: size.height * 0.078,
width: double.infinity,
decoration: BoxDecoration(
borderRadius:
BorderRadius.only(
bottomRight:
Radius.circular(
10.0),
bottomLeft:
Radius.circular(
10.0),
),
color: Colors.grey[600]
.withOpacity(0.5)),
child: Center(
child: AutoSizeText(
"${listt[index]["name"]}",
textAlign:
TextAlign.center,
style: GoogleFonts.lora(
textStyle: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
maxLines: 2,
),
),
),
],
),
margin: EdgeInsets.all(5.0),
decoration: BoxDecoration(
image: DecorationImage(
image: (listt[index]
["foto"] !=
null)
? OptimizedCacheImageProvider(
listt[index]["foto"])
: AssetImage(
"assets/images/n_image.jpg"),
fit: BoxFit.cover,
),
color: Colors.white,
borderRadius:
BorderRadius.circular(10.0),
),
),
),
);
},
),
And im listening controller in initstate with this;
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll <= delta) {
getmoreTarif();
}
});
Your current code tracks the last document of the current results and then calls startAfterDocument with that document to get the next set of results. This works for scrolling forward, but not when scrolling backward. To paginate backwards, you'll also need to track the first document of the current results and then call endBeforeDocument with that document.
Related
I am having an issue with a slider for an audioplayer in flutter. It is not updating while audio is playing, and cannot be moved at all. The debugprint for position is always showing 0:00.
Here is where the slider is built:
Future<Widget> slider() async {
var player = AudioPlayer();
AudioPlayerController apc = AudioPlayerController(selectedPath);
await apc.setSource(selectedPath);
var _position = apc.position;
var _duration = apc.duration;
// var _position =
debugPrint('$_position');
debugPrint('$_duration');
return Slider(
activeColor: AppColor.AppDarkBlueColor,
divisions: _duration?.inSeconds.toInt() ?? 1,
value: _position.inMilliseconds.toDouble() * .001,
min: 0.0,
max: _duration?.inSeconds.toDouble() ?? 0.0,
onChanged: (double value1) {
Duration _newPosition = Duration(seconds: value1.toInt());
setState(() {
player.seek(_newPosition);
_position = _newPosition;
debugPrint('$_position');
});
});
}
And Here is where the slider is implemented into the page:
bottomNavigationBar: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: AppColor.AppLightBlueColor),
height: (MediaQuery.of(context).size.height / 6),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: FutureBuilder(
future: slider(),
builder: (context, data) {
if (data.hasData) {
dynamic slide = data.data ?? [];
return (Container(
child: slide,
));
} else {
return Text("");
}
},
)),
I can see the position is updating elsewhere in the code, but I am not sure why it isn't updating with the slider. The position is set using audioplayer's .position argument.
I don't think it can work this way: A Future only returns a single event, so there will never be any updates.
I think what you need here is a StateFulWidget to hold the player, and a StreamBuilder or ValueListanableBuilder to update your slider.
First of all I don't know what i am facing, but I'll do my best to explain the situation.
I'm trying to build chat app and i have two sections on same page. These two different sections are rendering inside same ListView. Only thing that changing is the data which i am using to feed the ListView. I need to get the status of user in real time so i am putting tagged controllers for each tile which is rendering inside list view. Here comes the problem. The tiles rendered at the same index are not showing the true states of themselves until some state changes on that tile for example position of any Stack item.
Here is the code.
In this part I'm rendering ListView
ListView.builder(
itemCount: chatController.currentChats!.length,
itemBuilder: (context, index) {
return GetBuilder<UserOnlineController>(
global: false,
init: Get.find<UserOnlineController>(tag: chatController.currentUserID == chatController.currentChats![index].user1 ? chatController.currentChats![index].user2 : chatController.currentChats![index].user1),
builder: (userController) {
return Stack(
children: [
Positioned(
child: Container(
color: Colors.black,
width: Get.width,
height: Dimensions.h100,
child: Center(
child: Text(
"${userController.user!.name!}",
style: TextStyle(
color: Colors.white
),
),
),
)
)
],
);
}
);
}),
This is the part that I'm putting controllers and listening chats in real time.
void listenChats() async {
var chatController = Get.find<ChatController>();
var messagesController = Get.find<MessagesController>();
String userID = Get.find<SharedPreferenceService>().getUserID();
var currentUserDoc = (await firestoreService.getCollection('users').where('userID', isEqualTo: userID).get()).docs[0];
Stream<DocumentSnapshot> userStream = firestoreService.getCollection('users').doc(currentUserDoc.id).snapshots();
Stream<QuerySnapshot> chatStream = firestoreService.getCollection('chats').snapshots();
await for(var user in userStream){
var userObject = UserModel.fromJson(user.data() as Map<String,dynamic>);
await for(var chats in chatStream) {
List<Chat> activeChats = [];
List<Chat> unActiveChats = [];
List<Chat> newMatches = [];
List<Chat> allChats = [];
var filteredChats = chats.docs.where((chat) => userObject.chat!.active_chats!.contains(chat['chatID'])).toList();
filteredChats.forEach((chatDoc) {
var currentChat = Chat.fromJson(chatDoc.data() as Map<String,dynamic>);
if(currentChat.user1 == userID){
Get.put(
UserOnlineController(firestoreService: firestoreService, userID: currentChat.user2!),
tag: currentChat.user2!,
);
}
else{
Get.put(
UserOnlineController(firestoreService: firestoreService, userID: currentChat.user1!),
tag: currentChat.user1!
);
}
allChats.add(currentChat);
if(currentChat.isActive!){
if(currentChat.isStarted!){
activeChats.add(currentChat);
}
else{
newMatches.add(currentChat);
}
}
else{
unActiveChats.add(currentChat);
}
});
messagesController.generatePositions(activeChats.length, true);
messagesController.generatePositions(unActiveChats.length, false);
chatController.setAllChats(allChats);
chatController.setCurrentChats();
chatController.setChats(activeChats, unActiveChats, newMatches);
}
}
}
And this is the part that I'm using to control the UI state
void setAllChats(List<Chat> allChats) {
_allChats = allChats;
}
void setCurrentChats() {
_currentChats = _allChats!.where((chat) => chat.isActive! == isActiveMessages).toList();
update();
}
void setIsActiveMessages(bool state){
_isActiveMessages = state;
_currentChats = _allChats!.where((chat) => chat.isActive! == state).toList();
update();
}
In the above pictures all of these users are different but only true one is the third one at second screen shot.
Hello again this question basically explains all the details.
Multiple Instance with GetX tag not working in flutter
Basically you need to add key parameter.
GetBuilder<UserChatController>(
key: Key(currentUserControllerTag),
tag: currentUserControllerTag,
global: false,
init: Get.find<UserChatController>(tag: currentUserControllerTag),
builder: (controller) {
return controller.user != null ? Container(
width: Get.width,
height: Dimensions.h100,
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
child: Container(
height: Dimensions.h100,
width: double.maxFinite,
color: Colors.black,
child:Center(
child: Text(
controller.user != null ? controller.user!.name! : "",
style: TextStyle(
color: Colors.white
),
),
)
))
],
),
) : Container();
},
)
So far, I have made a loading animation that includes Container, Column, Text, and Image(gif). And this code is working fine in a new project but when I try to implement this code into my ongoing project, it is not working!
It does not throw any error but the loading screen is blank.
Like data from API loads without loading animation.
So, what I am doing wrong in this, or how to implement loader correctly?
Loader's code:
return Scaffold(
body: Container(
height: 130,
width: 135,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(17),
border: Border.all(color: Colors.blue)),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"Loading...",
style: TextStyle(
fontSize: 13,
letterSpacing: 2.1,
color: Colors.blue,
),
),
Padding(
padding: const EdgeInsets.all(3.0),
child: Image.network(
"https://cdn.dribbble.com/users/42716/screenshots/3913441/media/4ef7d67070fee7ab75948280f51d369f.gif",
height: 100,
),
),
],
),
),
);
Here I am implementing code:
Future<List<VeReportModel>>? _vReport() async {
debugPrint("Do not exploit this code $fDate &&&&&&&& Thank you");
try {
if (await NetworkUtils.isNetworkAvailable()) {
//UIUtils.showProcessIndicator(); //TODO This is old Indicator I want a new one here
Loader();
ApiResponse? apiResponse = await RestApiClient(session: session)
.vReport(fDate, tDate, spedLimit, stLimit, vId!);
UIUtils.hideProcessIndicator(); //TODO Old Indicator disposed
Common.printWrapped('Map: _vEvent(): apiResponse=$apiResponse');
if (apiResponse!.successful && apiResponse.code == apiSuccessCode) {
if (apiResponse.data != null) {
List mapMessages = apiResponse.result;
debugPrint("mapMessage $mapMessages");
return mapMessages
.map((v) => VeReportModel.fromJson(v))
.toList();
}
}
} else {
UIUtils.hideProcessIndicator();
UIUtils.displayInternetError(context);
}
} catch (e,stack) {
UIUtils.hideProcessIndicator();
UIUtils.catchErrorMsg(context);
debugPrint('Error While acknowledging v report : $e');
GeneralException.handleError(e, stack: stack, module: moduleVeReport);
debugPrint('VeReport: Error while getting v list: $e');
}
return [];
}
I have a SliverGrid. I have a search field. In my search field onChange event I have a function that searches my local sqlite db based on the keyword entered by the user returns the results and reassigns to a variable and calls notifyListeners(). Now my problem is for some weird reason whenever I search for an item the wrong item is rendered.
I checked the results from my functions by iterating over the list and logging the title and the overall count as well and the results were correct however my view always rendered the wrong items. Not sure how this is possible.
I also noticed something strange, whenever it rendered the wrong item and I went back to my code and hit save, triggering live reload, when I switched back to my emulator it now displayed the right item.
I have tried the release build on an actual phone and it's the same behaviour. Another weird thing is sometimes certain items will duplicate and show twice in my list while the user is typing.
This is my function that searches my sqlite db:
Future<List<Book>> searchBookshelf(String keyword) async {
try {
Database db = await _storageService.database;
final List<Map<String, dynamic>> rows = await db
.rawQuery("SELECT * FROM bookshelf WHERE title LIKE '%$keyword%'; ");
return rows.map((i) => Book.fromJson(i)).toList();
} catch (e) {
print(e);
return null;
}
}
This is my function that calls the above function from my viewmodel:
Future<void> getBooksByKeyword(String keyword) async {
books = await _bookService.searchBookshelf(keyword);
notifyListeners();
}
This is my actual view where i have the SliverGrid:
class BooksView extends ViewModelBuilderWidget<BooksViewModel> {
#override
bool get reactive => true;
#override
bool get createNewModelOnInsert => true;
#override
bool get disposeViewModel => true;
#override
void onViewModelReady(BooksViewModel vm) {
vm.initialise();
super.onViewModelReady(vm);
}
#override
Widget builder(BuildContext context, vm, Widget child) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height) / 4.3;
final double itemWidth = size.width / 3;
var heading = Container(
margin: EdgeInsets.only(top: 35),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Books',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
),
Text(
'Lorem ipsum dolor sit amet.',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 14),
),
],
),
),
);
var searchField = Container(
margin: EdgeInsets.only(top: 5, left: 15, bottom: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 1.0,
spreadRadius: 0.0,
offset: Offset(2.0, 1.0), // shadow direction: bottom right
),
],
),
child: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
FlutterIcons.search_faw,
size: 18,
),
suffixIcon: Icon(
FlutterIcons.filter_fou,
size: 18,
),
hintText: 'Search...',
),
onChanged: (keyword) async {
await vm.getBooksByKeyword(keyword);
},
onFieldSubmitted: (keyword) async {},
),
);
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 1, right: 1),
child: LiquidPullToRefresh(
color: Colors.amber,
key: vm.refreshIndicatorKey, // key if you want to add
onRefresh: vm.refresh,
showChildOpacityTransition: true,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
heading,
searchField,
],
),
),
SliverToBoxAdapter(
child: SpaceY(15),
),
SliverToBoxAdapter(
child: vm.books.length == 0
? Column(
children: [
Image.asset(
Images.manReading,
width: 250,
height: 250,
fit: BoxFit.contain,
),
Text('No books in your bookshelf,'),
Text('Grab a book from our bookstore.')
],
)
: SizedBox(),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid.count(
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: vm.books
.map((book) => BookTile(book: book))
.toList(),
),
)
],
),
))));
}
#override
BooksViewModel viewModelBuilder(BuildContext context) =>
BooksViewModel();
}
Now the reason I am even using SliverGrid in the first place is because I have a search field and a title above the grid and I want all items to scroll along with the page, I didn't want just the list to be scrollable.
I believe this odd behavior can be attributed to you calling vm.getBooksByKeyword() in onChanged. As this is an async method, there is no guarantee that the last result returned will be the result for the final text in the TextFormField. The reason you see the correct results after a live reload is because the method is being called again with the full text currently in the TextFormField.
The quickest way to verify this is to move the function call to onFieldSubmitted or onEditingComplete and see if it behaves correctly.
If you require calling the function with every change to the text, you will need to add a listener to the controller and be sure to only make the call after input has stopped for a specified amount of time, using a Timer, like so:
final _controller = TextEditingController();
Timer _timer;
...
_controller.addListener(() {
_timer?.cancel();
if(_controller.text.isNotEmpty) {
// only call the search method if keyword text does not change for 300 ms
_timer = Timer(Duration(milliseconds: 300),
() => vm.getBooksByKeyword(_controller.text));
}
});
...
#override
void dispose() {
// DON'T FORGET TO DISPOSE OF THE TextEditingController
_controller.dispose();
super.dispose();
}
...
TextFormField(
controller: controller,
...
);
So I found the problem and the solution:
The widget tree is remembering the list items place and providing the
same viewmodel as it had originally. Not only that it also takes every
item that goes into index 0 and provides it with the same data that
was enclosed on the Construction of the object.
Taken from here.
So basically the solution was to add and set a key property for each list item generated:
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
),
delegate: SliverChildListDelegate(vm.books
.map((book) => BookTile(
key: Key(book.id.toString()), book: book))
.toList()),
),
)
And also here:
const BookTile({Key key, this.book}) : super(key: key, reactive: false);
My search works perfectly now. :)
I'm having an issue with my app, I'm creating a flutter app to track cryptocurrency prices.
The issue is that I get the data properly from the API, then I print it into the counsel but when I try to display it inside the app, it displays null.
Here is the code I use to get the data from the API
class CurrencyData { var decodedData;
Future getCoinsData() async {
http.Response response =
await http.get(coinUrl);
if (response.statusCode == 200) {
decodedData = jsonDecode(response.body);
} else {
print(response.statusCode);
throw 'Problem with the request, try again later!';
}
return decodedData;
}
}
Here is the code where I call the data to display it.
class _DashboardPageState extends State<DashboardPage> {
CurrencyData currencyData = CurrencyData();
var btcPrice;
var btcChange24h;
void cryptoCurrencyData() async {
var data = await currencyData.getCoinsData();
print(btcPrice = data['data'][0]['priceUsd']);
print(btcChange24h = data['data'][0]['changePercent24Hr']);
}
#override
void initState() {
super.initState();
cryptoCurrencyData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
// the top bar
Container(
padding: EdgeInsets.all(40),
constraints: BoxConstraints.expand(height: 175),
decoration: BoxDecoration(
color: Colors.lightBlue,
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 20.0,
// has the effect of softening the shadow
spreadRadius:
5.0, // has the effect of extending the shadow
),
],
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
child: Container(
padding: EdgeInsets.only(top: 25),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Text(
'Crypto Tracker',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
)
],
),
),
),
// the body part
CurrencyWidget(
currencyIconUrl: 'assets/images/btc.png',
currencyName: 'Bitcoin',
currencyShortName: 'BTC',
currencyPrice: btcPrice,
currencyChange24h: btcChange24h,
),
I get the data printed into the console but I also get Null displayed in the emulator as shown in the below screenshot.
The image where null is displayed
A screenshot of the data being printed in the console
Any idea what the issue may be?
The problem is that getting api data is async task so it takes time, while build method build screen in that time, so it is printing null.
1) You can call setState at the end of function which change null to actual data when it gets from API.
void cryptoCurrencyData() async {
var data = await currencyData.getCoinsData();
btcPrice = data['data'][0]['priceUsd']; // assign
btcChange24h = data['data'][0]['changePercent24Hr']; // aasign
print(btcPrice = data['data'][0]['priceUsd']);
print(btcChange24h = data['data'][0]['changePercent24Hr']);
setState(() {}); // added
}
2) However, FutureBuilder is more better option where you can show loading indicator or something which shows data is loading and display when arrives.
Note: in this way you don't need cryptoCurrencyData method and also you don't need to store value in different variable.
FutureBuilder(
future: currencyData.getCoinsData(),
builder: (_, sanpshot) {
if (!sanpshot.hasData) {
return CircularProgressIndicator();
}
return CurrencyWidget(
currencyIconUrl: 'assets/images/btc.png',
currencyName: 'Bitcoin',
currencyShortName: 'BTC',
currencyPrice: data['data'][0]['priceUsd'],
currencyChange24h: data['data'][0]['changePercent24Hr'],
);
},
),