I am stacked!! and i know i will find help here . I create a flutter application which fetches data from news.org API . Everything was working fine until i started implementing BLOC in the app . I have successfully implemented the first part with BLOC with fetches all the data from the API . the next thing to do is to fetch another data using the categories provided by the API in another page using BLOC .
For instance , there are categories like business , technology , finance etc . So main thing is when the user taps on any of the category the data show be fetched from the API using BLOC .
the following are the codes for the bloc ...
THIS IS THE ERROR I GET
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building BlocListener<ArticleBloc, ArticleState>(dirty, state: _BlocListenerBaseState<ArticleBloc, ArticleState>#aae07):
A build function returned null.
The offending widget is: BlocListener<ArticleBloc, ArticleState>
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The relevant error-causing widget was:
BlocListener<ArticleBloc, ArticleState> file:///C:/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.1.1/lib/src/bloc_builder.dart:149:12
When the exception was thrown, this was the stack:
#0 debugWidgetBuilderValue. (package:flutter/src/widgets/debug.dart:302:7)
#1 debugWidgetBuilderValue (package:flutter/src/widgets/debug.dart:323:4)
#2 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4632:7)
#3 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
#4 Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
RepositoRy
abstract class CategoryRepository {
Future<List<Article>> getCategory(String category);
}
class CatService implements CategoryRepository {
#override
Future<List<Article>> getCategory(String category) async {
// List<Article> categoryNewsList = [];
String url =
"http://newsapi.org/v2/top-headlines?country=us&category=$category&apiKey=df74fc47f0dd401bb5e56c34893a7795";
return getData(url);
/*var response = await http.get(url);
//decode the response into a json object
var jsonData = jsonDecode(response.body);
//check if the status of the response is OK
if (jsonData["status"] == "ok") {
jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
if (item["urlToImage"] != null && item["description"] != null) {
//create an object of type NewsArticles
Article newsArticleModel = new Article(
author: item["author"],
title: item["title"],
description: item["description"],
url: item["url"],
urlToImage: item["urlToImage"],
content: item["content"]);
//add data to news list
categoryNewsList.add(newsArticleModel);
}
});
}
return categoryNewsList;*/
}
}
Future<List<Article>> getData(String url) async {
List<Article> items = [];
var response = await http.get(url);
//decode the response into a json object
var jsonData = jsonDecode(response.body);
//check if the status of the response is OK
if (jsonData["status"] == "ok") {
jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
if (item["urlToImage"] != null && item["description"] != null) {
//create an object of type NewsArticles
Article article = new Article(
author: item["author"],
title: item["title"],
description: item["description"],
url: item["url"],
urlToImage: item["urlToImage"],
content: item["content"]);
//add data to news list
items.add(article);
}
});
}
return items;
}
Bloc
class ArticleBloc extends Bloc<ArticleEvent, ArticleState> {
CategoryRepository categoryRepository;
ArticleBloc({this.categoryRepository}) : super(ArticleInitial());
#override
Stream<ArticleState> mapEventToState(
ArticleEvent event,
) async* {
if (event is GetArticle) {
try {
yield ArticleLoading();
final articleList =
await categoryRepository.getCategory(event.category);
yield ArticleLoaded(articleList);
} catch (e) {
print(e.message);
}
}
}
}
Event
class GetArticle extends ArticleEvent{
final String category;
GetArticle(this.category);
}
States
#immutable
abstract class ArticleState {
const ArticleState();
}
class ArticleInitial extends ArticleState {
const ArticleInitial();
}
class ArticleLoading extends ArticleState {
const ArticleLoading();
}
class ArticleLoaded extends ArticleState {
final List<Article> articleList;
ArticleLoaded(this.articleList);
}
class ArticleError extends ArticleState {
final String error;
ArticleError(this.error);
#override
bool operator ==(Object object) {
if (identical(this, object)) return true;
return object is ArticleError && object.error == error;
}
#override
int get hashCode => error.hashCode;
}
UI
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'News app',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BlocProvider(
child: TestCat(),
create: (context) => ArticleBloc(categoryRepository : CatService()),
),
);
}
}
tEST category page
class TestCat extends StatefulWidget {
#override
_TestCatState createState() => _TestCatState();
}
class _TestCatState extends State<TestCat> {
bool isLoading = true;
List<String> categoryItems;
#override
void initState() {
super.initState();
categoryItems = getAllCategories();
// getCategory(categoryItems[0]);
// getCategoryNews();
}
getCategory(cat) async {
context.bloc<ArticleBloc>().add(GetArticle(cat));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: header(context, isAppTitle: false, title: "App"),
body: _newsBody(context),
);
}
_newsBody(context) {
return ListView(
children: [
//category list
Container(
padding:
EdgeInsets.symmetric(horizontal: NewsAppConstants().margin16),
height: NewsAppConstants().columnHeight70,
child: ListView.builder(
itemCount: categoryItems.length,
itemBuilder: (context, index) {
return TitleCategory(
title: categoryItems[index],
onTap: ()=> callCat(context, categoryItems[index]),
);
},
shrinkWrap: true,
scrollDirection: Axis.horizontal,
),
),
Divider(),
Container(
child: BlocBuilder<ArticleBloc, ArticleState>(
builder: (context, ArticleState articleState) {
//check states and update UI
if (articleState is ArticleInitial) {
return buildInput(context);
} else if (articleState is ArticleLoading) {
return Loading();
} else if (articleState is ArticleLoaded) {
List<Article> articles = articleState.articleList;
updateUI(articles);
} else if (articleState is ArticleError) {
// shows an error widget when something goes wrong
final error = articleState.error;
final errorMsg = "${error.toString()}\nTap to retry";
ShowErrorMessage(
errorMessage: errorMsg,
onTap: getCategory,
);
}
return buildInput(context);
}),
),
],
);
}
getAllCategories() {
List<String> categoryList = [
"Business",
"Entertainment",
"General",
"Sports",
"Technology",
"Health",
"Science"
];
return categoryList;
}
Widget updateUI(List<Article> newsList) {
return SingleChildScrollView(
child: Column(
children: [
Container(
child: ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: newsList.length,
itemBuilder: (context, index) {
return NewsBlogTile(
urlToImage: newsList[index].urlToImage,
title: newsList[index].title,
description: newsList[index].description,
url: newsList[index].url,
);
}),
),
Divider(),
],
));
}
buildInput(context) {
ListView.builder(
itemCount: categoryItems.length,
itemBuilder: (context, index) {
return TitleCategory(
title: categoryItems[index],
onTap: () {
print("tapped");
// callCat(context, categoryItems[index]);
},
);
},
shrinkWrap: true,
scrollDirection: Axis.horizontal,
);
}
callCat(BuildContext context, String cat) {
print(cat);
context.bloc<ArticleBloc>().add(GetArticle(cat));
}
}
//this displays the data fetched from the API
class NewsBlogTile extends StatelessWidget {
final urlToImage, title, description, url;
NewsBlogTile(
{#required this.urlToImage,
#required this.title,
#required this.description,
#required this.url});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
child: Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: EdgeInsets.all(NewsAppConstants().margin8),
child: Column(
children: <Widget>[
ClipRRect(
borderRadius:
BorderRadius.circular(NewsAppConstants().margin8),
child: Image.network(urlToImage)),
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black,
fontSize: NewsAppConstants().margin16),
),
SizedBox(
height: NewsAppConstants().margin8,
),
Text(
description,
style: TextStyle(color: Colors.black54),
)
],
),
),
Divider(),
],
),
),
);
}
}
//news title category
class TitleCategory extends StatelessWidget {
final title;
final Function onTap;
TitleCategory({this.title, this.onTap});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap,
child: Container(
margin: EdgeInsets.all(NewsAppConstants().margin8),
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(NewsAppConstants().margin8),
child: Container(
child: Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: NewsAppConstants().font16,
fontWeight: FontWeight.w500),
),
alignment: Alignment.center,
width: NewsAppConstants().imageWidth120,
height: NewsAppConstants().imageHeight60,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(NewsAppConstants().margin8),
color: Colors.black,
),
),
)
],
),
),
);
}
}
from what I see
you might try one of the below solutions :
try in bloc builder to return Container and inside it handle you states like this :
builder: (context, state) {
return Container(
child: Column(
children: [
if (state is Loading)
CircularProgressIndicator(),
if (state is Loaded)
CatsListView(data: state.data),
try to cover all your kind of states in if/else in your Bloc builder
so let's assume that you have 2 states (state1, state2) so your Bloc builder
would be something like this
builder: (context, state) {
if (state is state1) return Container();
else if (state is state2) return Container();
else return Container();
note that if you covered all states you don't have to do the last else
Related
I am Calling an Api to filter Countries list with Region. Whenever I Select the region it filter and show all countries of the selected region. I'm using searchable_dropdown package to filter.
My Searchable Dropdown Filter is Working Fine But Not getting All Data Without Selecting from Dropdown.
What i want :- i want to show all Available data from api when app runs or when no region is selected from dropdown as in Screenshot 1.
My Codes are Below
Main.dart
import 'package:flutter/material.dart';
import 'package:searchable_dropdown_flutter/searchable_dropdown1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SearchableDropdownApp(),
);
}
}
searchable_dropdown.dart
import 'package:flutter/material.dart';
import 'package:searchable_dropdown/searchable_dropdown.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
class SearchableDropdownApp extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<SearchableDropdownApp> {
Map<String, String> selectedValueMap = Map();
// ignore: deprecated_member_use
List filteredData = List();
// ignore: deprecated_member_use
List filteredDataAll = List();
// ignore: deprecated_member_use
List alldata = List();
#override
void initState() {
selectedValueMap["server"] = null;
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text('Searchable Dropdown Example App'),
),
body: FutureBuilder(
// get data from server and return a list of mapped 'name' fields
future:
getServerData(), //sets getServerData method as the expected Future
// ignore: missing_return
builder: (context, snapshot) {
if (snapshot.hasData) {
// ignore: deprecated_member_use
List<String> countries = new List();
for (int i = 0; i < snapshot.data.length; i++) {
if (!countries.contains(snapshot.data[i]['region']))
countries.add(snapshot.data[i]['region']);
}
alldata = snapshot.data;
//checks if response returned valid data
// use mapped 'name' fields for providing options and store selected value to the key "server"
return getSearchableDropdown(countries, "server", alldata);
} else if (snapshot.hasError) {
//checks if the response threw error
return Text("${snapshot.error}");
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
Widget getSearchableDropdown(List<String> listData, mapKey, alldata) {
List<DropdownMenuItem> items = [];
for (int i = 0; i < listData.length; i++) {
items.add(new DropdownMenuItem(
child: new Text(
listData[i],
),
value: listData[i],
));
}
return Scaffold(
body: Column(
children: [
SearchableDropdown(
isExpanded: true,
style: TextStyle(color: Colors.red),
items: items,
value: selectedValueMap[mapKey],
isCaseSensitiveSearch: false,
hint: new Text('Select Country'),
searchHint: new Text(
'Select Country',
style: new TextStyle(fontSize: 20),
),
onChanged: (value) {
setState(() {
selectedValueMap[mapKey] = value;
print('selectedValueMap[mapKey]');
print(selectedValueMap[mapKey]);
// ignore: deprecated_member_use
filteredData = new List();
for (int i = 0; i < alldata.length; i++) {
if (alldata[i]['region'] == selectedValueMap[mapKey])
filteredData.add(alldata[i]);
}
filteredDataAll = filteredData.toList();
print('filteredDataAll ka data');
});
},
),
Text(selectedValueMap[mapKey].toString()),
Text('data'),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: filteredDataAll.length,
itemBuilder: (context, index) {
return SingleChildScrollView(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(filteredDataAll[index].toString()),
],
),
)),
);
}),
),
),
],
),
);
}
Future<List> getServerData() async {
String url =
'https://restcountries.eu/rest/v2/all?fields=name;capital;alpha3Code;region;population;';
final response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
List<dynamic> responseBody = json.decode(response.body);
return responseBody;
} else {
print("error from server : $response");
throw Exception('Failed to load post');
}
}
}
SecreenShots
When App Run for First Time
SecreenShots 1
After Selecting from Filter
SecreenShots 2
try this its works fine for me...
FutureBuilder<List<AllAccountModel>>(
future: accountData,
builder: (context, snapshot){
if(snapshot.hasData){
List<String> dropDown=List.filled(snapshot.data!.length, '',growable: true);
print(snapshot.data!.length);
for(int i=0;i<=snapshot.data!.length-1;i++){
dropDown[i]=snapshot.data![i].accountName+"\n"+snapshot.data![i].phoneNo;
}
return DropdownSearch<String>(
mode: Mode.MENU,
items: dropDown,
showSearchBox: true,
label: "Account Name",
onChanged: print,
);
}else{
DropdownSearch<String>(
mode: Mode.MENU,
items: [snapshot.error.toString()],
showSearchBox: true,
label: "Account Name",
onChanged: print,
);
}
return
const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child:
CircularProgressIndicator(),
),
);
})
call Api in initState
#override void initState() {
// TODO: implement initState
super.initState();
accountData = allAccounts();}
my Api Function
Future<List<AllAccountModel>> allAccounts() async {
return HttpService().getAllAccounts();}
End Result
I have a problem with Future builder in Flutter. It gets the info from api successfully but doesn't show it. When I put print and print the info from api, it is ok and it shows the movies name without any problems. here is my code:
class Search extends StatefulWidget {
final String value;
Search({Key key, String this.value}) : super(key: key);
#override
_SearchState createState() => _SearchState();
}
class _SearchState extends State<Search> {
var title;
Future getSearch({index}) async {
http.Response response = await http.get(
'https://api.themoviedb.org/3/search/company?api_key=6d6f3a650f56fd6b3347428018a20a73&query=' +
widget.value);
var results = json.decode(response.body);
setState(() {
this.title = results['results'];
});
return title[index]['name'];
}
getName(index) {
return title[index]['name'];
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Color(0xff1d1d27),
body: Column(
children: [
Expanded(
child: FutureBuilder(
initialData: [],
future: getSearch(),
builder: (context, snapshot) {
return ListView.builder(itemBuilder: (context, index) {
Padding(
padding:
EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Container(
color: Colors.white,
child: Text(getName(index).toString()),
),
);
});
},
))
],
)),
);
}
}
Please Use this code, It works fine to fetch the names and show them on the list,
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Search extends StatefulWidget {
final String value;
Search({Key key, String this.value}) : super(key: key);
#override
_SearchState createState() => _SearchState();
}
class _SearchState extends State<Search> {
var title;
var results;
getSearch() async {
http.Response response = await http.get(
'https://api.themoviedb.org/3/search/company?api_key=6d6f3a650f56fd6b3347428018a20a73&query=' +
widget.value);
results = json.decode(
response.body); //make it global variable to fetch it everywhere we need
return results['results'][0]['name'];
}
getName(index) {
return results['results'][index]['name'];
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Color(0xff1d1d27),
body: Column(
children: [
Expanded(
child: FutureBuilder(
// initialData: [],
future: getSearch(),
builder: (context, snapshot) {
String name =
snapshot.data; // to get the data from the getSearch
print(name);
if (snapshot.hasData) {
// if there is data then show the list
return ListView.builder(
itemCount: results['results']
?.length, // to get the list length of results
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 30, vertical: 20),
child: Container(
color: Colors.white,
child: Text(getName(index)
.toString()), // pass the index in the getName to get the name
),
);
});
} else {
// if there is no data or data is not loaded then show the text loading...
return new Text("Loading...",
style: TextStyle(fontSize: 42, color: Colors.white));
}
},
))
],
)),
);
}
}
P.S
To Learn the basics of Futurebuilder You can see this article For more learning
I have commented the code to explain more to you.
In my Flutter application I am using Provider version 4.0.4 to manage the state of my app. In basic terms, my app will list down the nearby companies with their rating. users can select a organisation, open it and add their rating as well, so the final rating will be updated. I am using the Consumer concept in Provider to handle the tasks.
In NearByPlacesPage class I am listing down the companies around me with rating information. User can click on a company and they will be taken to OrganizationPage page.
In OrganizationPage class, the rating is displayed again. user can add their rating to the system. Then the rating information in both OrganizationPage page and NearByPlacesPage (back page) need to be updated.
The issue is, when the user update the rating, the rating in OrganizationPage get updated but not NearByPlacesPage in back stack. When we go back to NearByPlacesPage, we can clearly see the old rating values. The page need to be reloaded to get updated values.
Below are the important sections in my code
NearByPlacesPage
class NearByPlacesPage extends StatelessWidget {
int orgTypeID;
String orgTypeName;
NearByPlacesPage(this.orgTypeID, this.orgTypeName);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => RatingService()),
],
child: SingleChildScrollView(
child: _NearByPlacesPageUI(orgTypeID, orgTypeName),
),
),
appBar: AppBar(
title: Text(orgTypeName),
),
);
}
}
class _NearByPlacesPageUI extends StatefulWidget {
int orgTypeID;
String orgTypename;
_NearByPlacesPageUI(this.orgTypeID, this.orgTypename);
#override
State<StatefulWidget> createState() {
return _NearByPlacesPageState();
}
}
class _NearByPlacesPageState extends State<_NearByPlacesPageUI> {
#override
Widget build(BuildContext context) {
Consumer<RatingService>(builder: (context, data, child){
return Flexible(
child: ListView.builder(
itemCount: orgList.length,
itemBuilder:(BuildContext context, int index) {
Organization organization = orgList[index];
if (organization.isDisabled != true) {
RatingValue ratingValue = data.getData();
return Container(
margin: EdgeInsets.only(
top: 5, left: 5, right: 5),
child: _buildPlace(organization, ratingValue));
} else {
return Container();
}
},),
);
},);
}
}
OrganizationPage
class OrganizationPage extends StatelessWidget {
Organization organization;
String orgTypeName;
OrganizationPage(this.organization, this.orgTypeName);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: _OrganizationPageUI(organization, orgTypeName),
),
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(organization.name),
),
);
}
}
class _OrganizationPageUI extends StatefulWidget {
Organization organization;
String orgTypeName;
_OrganizationPageUI(this.organization, this.orgTypeName);
#override
State<StatefulWidget> createState() {
return _OrganizationPageState();
}
}
class _OrganizationPageState extends State<_OrganizationPageUI> {
#override
Widget build(BuildContext context) {
Consumer<RatingService>(
builder: (context, data, child) {
Consumer<RatingService>(
return Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 10, left: 10),
child: Text(daa.getData()
style: Theme.of(context).textTheme.bodyText2.apply(color: Colors.grey),
),
),
],
);
),
}
}
}
In OrganizationPage there is a AlerDialog, which allows the user to rate and save. When saved, it will call another method which will reload the data.
Widget _ratingDialog(double _rating) {
RatingService _ratingService =
Provider.of<RatingService>(context, listen: false);
Rating _rating = _ratingService.returnRating();
double _ratingValue = _ratingService.returnRating().rating;
return AlertDialog(
title: const Text("Your Rating"),
actions: [
new FlatButton(
child: const Text("Save"),
//onPressed: () => Navigator.pop(context),
onPressed: () async {
Rating rating = Rating(
idrating:
_rating.idrating != null ? _rating.idrating : null,
user: _user,
organization: widget.organization,
rating: _ratingValue,
dateCreated: DateTime.now().millisecondsSinceEpoch,
lastUpdated: DateTime.now().millisecondsSinceEpoch);
await _ratingService.saveOrUpdateRating(rating, authToken);
_loadRatingByUserAndOrganization(authToken);
_loadRatingValueByOrganization(authToken);
Navigator.pop(context);
},
),
],
);
}
Future _loadRatingByUserAndOrganization(String authToken) {
RatingService _ratingService =Provider.of<RatingService>(context, listen: false);
return _ratingService.getRatingByUserAndOrganization(
_authService.getDatabaseUser().user.iduser,
widget.organization.idorganization,
authToken);
}
RatingService
This is the class which is responsible for calling notifyListeners(). It will be triggered by the above AlertDialog and the expected behaviour is to reload data in both OrganizationPage and NearByPlacesPage
class RatingService with ChangeNotifier {
List<RatingValue> _ratingValueList ;
List<RatingValue> getData()
{
return _ratingValueList;
}
//Load rating by user and Organization
Future<void> getRatingByUserAndOrganization(int idUser, int organizationID, String authToken) async {
try {
var data = await http.get(
_navLinks.getRatingByUserAndOrganization(idUser, organizationID),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
);
print(data.body);
_rating = Rating.fromJson(convert.json.decode(data.body));
notifyListeners();
} catch (error) {
print(error);
throw error;
}
}
}
What I have I done wrong?
I'm new to Flutter and need a bit of help. I've built a random joke generator app that reads data from the API and displays a new joke every time a button is pressed. I want to make the app show the previously fetched data when device is offline. I tried searching online but found nothing that does it using Flutter.
class _HomePageState extends State<HomePage> {
List data;
Future<Jokes> post;
String url="https://official-joke-api.appspot.com/random_joke";
var response;
Future<Jokes> getData() async {
response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
changeApi()
{
setState(() {
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
});
}
#override
void initState()
{
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
// TODO: implement build
return Scaffold(
key: key,
backgroundColor: Colors.amberAccent,
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FutureBuilder<Jokes>(
future:
getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//checks if the response returns valid data
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new Text(
snapshot.data.setup ,
style: TextStyle(fontFamily: "Rock Salt"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.setup, ));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
/
SizedBox(
height: 10.0,
),
new GestureDetector(
child: new Text(
" - ${snapshot.data.punchline}",
style: TextStyle(fontFamily: "Roboto"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.punchline));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
],
),
);
} else if (snapshot.hasError) {
//checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
SizedBox(
height: 25.0,
),
new RaisedButton(
onPressed: changeApi,
color: Colors.pinkAccent,
child: Text("Press for a new joke", style: TextStyle(color: Colors.white,)),
)
],
),
),
);
}
}
class Jokes {
final String setup;
final String punchline;
Jokes({this.setup, this.punchline});
factory Jokes.fromJson(Map<String, dynamic> json) {
return Jokes(setup: json['setup'], punchline: json['punchline']);
}
}
Api
Here's my full code: code
There are some videos about caching, here's the one from flutter team, and one from tensor programming channel.
You can use connectivity plugin to check whether the device is offline.
If device is offline, show data from shared_preferences or sqflite, if it's online, fetch new data (and of course update your cache).
I'm building an app for training in Flutter and I'm actually stuck in the filter functionality.
I have a ListView where I fetch data from TheMovieDB API and a ModalBottomSheet with three FilterChips for selecting the filter criteria (popular, top rated and latest movies).
And here's where I'm stuck. I want to call the "_loadNextPage()" method when the user presses the "Done" button in the ModalBottomSheet through "performUpdate()" but I can't do it because they're not in the same class.
I'll post the code down below for better understanding.
class _HomePageState extends State<HomePage> {
RequestProvider _requestProvider = new RequestProvider();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FluttieDB"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.filter_list),
onPressed: () => buildFilterBottomSheet(),
)
],
),
body: MovieList(_requestProvider, _currentFilter),
);
}
void buildFilterBottomSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
height: 150.0,
decoration: BoxDecoration(color: Colors.white),
child: Column(
children: <Widget>[
buildFilterTitle(context),
Expanded(
child: _FilterChipRow(),
),
],
),
);
});
}
Widget buildFilterTitle(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
alignment: Alignment.centerLeft,
height: 46.0,
decoration: BoxDecoration(color: Colors.blue),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
"Filter by",
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
OutlineButton(
onPressed: () => performUpdate(context),
padding: const EdgeInsets.all(0.0),
shape: const StadiumBorder(),
child: Text(
"Done",
style: TextStyle(color: Colors.white),
),
),
],
),
);
}
void performUpdate(BuildContext context) {
MovieList _movieList = new MovieList(_requestProvider, _currentFilter);
_movieList.createState()._loadNextPage();
Navigator.pop(context);
}
}
class MovieList extends StatefulWidget {
MovieList(this.provider, this.currentFilter, {Key key}) : super(key: key);
final RequestProvider provider;
final String currentFilter;
#override
_MovieListState createState() => new _MovieListState();
}
class _MovieListState extends State<MovieList> {
List<Movie> _movies = List();
int _pageNumber = 1;
LoadingState _loadingState = LoadingState.LOADING;
bool _isLoading = false;
_loadNextPage() async {
_isLoading = true;
try {
var nextMovies = await widget.provider
.provideMedia(widget.currentFilter, page: _pageNumber);
setState(() {
_loadingState = LoadingState.DONE;
_movies.addAll(nextMovies);
_isLoading = false;
_pageNumber++;
});
} catch (e) {
_isLoading = false;
if (_loadingState == LoadingState.LOADING) {
setState(() => _loadingState = LoadingState.ERROR);
}
}
}
#override
void initState() {
super.initState();
_loadNextPage();
}
#override
Widget build(BuildContext context) {
switch (_loadingState) {
case LoadingState.DONE:
return ListView.builder(
itemCount: _movies.length,
itemBuilder: (BuildContext context, int index) {
if (!_isLoading && index > (_movies.length * 0.7)) {
_loadNextPage();
}
return MovieListItem(_movies[index]);
});
case LoadingState.ERROR:
return Center(
child: Text("Error retrieving movies, check your connection"));
case LoadingState.LOADING:
return Center(child: CircularProgressIndicator());
default:
return Container();
}
}
}
As you can see, I did some experiments in the performUpdate() but it doesn't refresh the ListView with the selected option in the filters and I don't think it's the best way to achieve what I want.
Thanks and sorry if the question is a bit dumb. I'm a little bit newbie in Flutter.
Redux is a great state management library that originated with React and JS, but has been ported to Dart, and has a flutter specific library as well. Redux is a very powerful framework which uses a pub/sub system to allow your view to subscribe to changes to the model, while using a system of "actions" and "reducers" to update the model.
A great tutorial for getting up and running with Redux in Flutter can be found here
Alternatively you could look into the scoped model, which is another state management library for flutter. The scoped model is less capable, but for simple use cases may be more than adequate.
Further reading:
Understand and choose a state management solution
You Might Not Need Redux