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 [];
}
Related
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.
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'],
);
},
),
When I add new item to my list, it will overwrites earlier value. I want to append the list, but it is not happening
Container(
// color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Container(
width: 50,
height: 50,
// color: Colors.blue,
child: FlatButton(onPressed: (){
if(_itemCount > 0){
setState(() {
_itemCount--;
});
}
}, child: Image(image: AssetImage("images/minus.png"),width: 20,height: 20,),),
),
Container(
child: Text("$_itemCount"),
),
Container(
width: 50,
height: 50,
// color: Colors.green,
child: FlatButton(onPressed: () async{
setState(() {
_itemCount++;
});
cartItemList = [{"index":widget.intex,"itemObj":widget.items,"quantity":_itemCount}];
print(addedItems(cartItemList));
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList('itemList', addedItems(cartItemList));
}, child: Image(image: AssetImage("images/plus.png"),width: 20,height: 20,),),
),
],
),
),
I have a ListView, ListView items will fetch from API, when I click one item I want it add to a list, which I can save in sharedpreference. but every time I click on a button earlier one overwritten by new one.
below is the code to convert the data to JSON format
List<String> addedItems(List<dynamic> cartList){
try {
var res = cartList.map((v) => json.encode(v)).toList();
return res;
} catch (err) {
// Just in case
return [];
}
},
Output is
When I add first item
[{"itemObj":{"item_price":22.0,"item_name":"DINE SPECIAL BREAKFAST","item_img":" ","item_code":"001","item_discount":0.0,"item_id":552,"category_id":12},"quantity":1}]
When I add same item again
[{"itemObj":{"item_price":22.0,"item_name":"DINE SPECIAL BREAKFAST","item_img":" ","item_code":"001","item_discount":0.0,"item_id":552,"category_id":12},"quantity":2}]
(quantity increased)
When I add a new item
[{"itemObj":{"item_price":20.0,"item_name":"English Breakfast","item_img":" ","item_code":"002","item_discount":0.0,"item_id":71,"category_id":12},"quantity":1}]
(Where are the DINE SPECIALs?)
But I want output like
[{"itemObj":{"item_price":22.0,"item_name":"DINE SPECIAL BREAKFAST","item_img":" ","item_code":"001","item_discount":0.0,"item_id":552,"category_id":12},"quantity":2},{"item_price":20.0,"item_name":"English Breakfast","item_img":" ","item_code":"002","item_discount":0.0,"item_id":71,"category_id":12},"quantity":1}]
you set list every time you click button like this
cartItemList = [{"index":widget.intex,"itemObj":widget.items,"quantity":_itemCount}];
maybe you need to do is
cartItemList.add({"index":widget.intex,"itemObj":widget.items,"quantity":_itemCount});
I am trying to create a dynamic multilevel CupertinoPicker. When you select a location type from the first list, it displays the list of locations that match that type. This part is working fine, the problem is that if I swap to a different list of locations, the first Text widget of the second list of locations is indented according to the first Text widget of the first list of locations.
I've tried specifying that the Text widget should be aligned to the center using 'alignment: TextAlignment.center'. I also tried setting the location to null when swapping between location lists. Neither of these solved the problem or had any apparent effect.
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 5.0),
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 32.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
locationType = locationTypeList[selectedIndex];
});
},
children: pickerLocationType,
),
),
Container(
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 30.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
if (locationType == 'Campus') {
location = campusList[selectedIndex];
}
if (locationType == 'City') {
location = cityList[selectedIndex];
}
});
},
children: pickerMap[locationType],
),
),
The result should be that the first line is (imagine this set into a CupertinoPicker):
----------------------------------City 1--------------------------------------
----------------------------------City 2--------------------------------------
But it looks more like:
-------------------------------City 1-----------------------------------------
----------------------------------City 2--------------------------------------
If images are needed, I will edit this post with the link to them.
I have discovered the solution. See below:
Container(
key: ValueKey(this._locationType),
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 30.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
if (locationType == 'Campus') {
location = campusList[selectedIndex];
}
if (locationType == 'City') {
location = cityList[selectedIndex];
}
});
},
children: pickerMap[locationType],
),
),