Simple Query Example
For this example we will walk through the simplest form of caching with Cached Query.
We will use the Json Placeholder Api with a time delay to demonstrate cached data.
The source code for this example can be found here: https://github.com/D-James-GH/cached_query/tree/main/examples/simple_caching
The Setup
Install the package.
flutter pub add cached_query_flutter
The setup is optional but to take full advantage of cached query we need to call the config function as early as possible.
The config
function lets cached query know that it should re-fetch queries if the connectivity is established and if
the app comes back into view.
void main() {
WidgetsFlutterBinding.ensureInitialized();
CachedQuery.instance.configFlutter(
config: QueryConfigFlutter(
refetchOnResume: true,
refetchOnConnection: true,
),
);
runApp(const MyApp());
}
The main app will just consist of one page.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: PostPage(),
);
}
}
Creating the Query
We create a service function which returns a Query
for us to display. The queryFn
is where the logic for the request
needs to go. This function will be first called when a listener is added to the query stream.
As the app is going to fetch a post by an id we have to add the id to the query key as well. The helper function below returns a key which includes the post id.
String postKey(int id) => "postKey$id";
Each time the query key changes a new query will be created.
Query<PostModel> getPostById(int id) {
return Query<PostModel>(
key: postKey(id),
queryFn: () async {
final uri = Uri.parse(
'https://jsonplaceholder.typicode.com/posts/$id',
);
final res = await http.get(uri);
return Future.delayed(
const Duration(milliseconds: 500),
() => PostModel.fromJson(
jsonDecode(res.body) as Map<String, dynamic>,
),
);
},
);
}
Post Model
This post model is a simple object that we serialize the json payload into.
class PostModel {
final String title;
final int id;
final String body;
final int userId;
PostModel({
required this.title,
required this.id,
required this.body,
required this.userId,
});
factory PostModel.fromJson(Map<String, dynamic> json) => PostModel(
title: json["title"],
body: json["body"],
id: json["id"],
userId: json["userId"],
);
}
The UI
The UI will consist of one page. This page will keep the current post id in local state and then increment or decrement the post id if the user chooses.
We are passing the QueryBuilder
the query created above. The builder will call the builder function whenever a new
QueryState
is emitted.
Two query builders are being used, one in the app bar to display the loading and one in the body to display the post. Given
the same id getPostById
will always return the same instance of Query
and therefore there is no need to store the
query in a variable. We can just use getPostById
in multiple places.
class PostPage extends StatefulWidget {
const PostPage({Key? key}) : super(key: key);
State<PostPage> createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
int currentId = 50;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: QueryBuilder(
query: service.getPostById(currentId),
builder: (context, state) {
return Text(
state.status == QueryStatus.loading ? "loading..." : "",
);
},
),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _refreshPost,
)
],
),
body: Center(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () => setState(() => currentId = currentId - 1),
icon: const Icon(Icons.arrow_left),
),
Text(currentId.toString()),
IconButton(
onPressed: () => setState(() => currentId = currentId + 1),
icon: const Icon(Icons.arrow_right),
),
],
),
Post(id: currentId),
],
),
),
);
}
void _refreshPost() {
service.getPostById(currentId).refetch();
}
}
Post Widget
The Post widget is just responsible for displaying the post with a given id.
class Post extends StatelessWidget {
final int id;
const Post({required this.id, super.key});
Widget build(BuildContext context) {
return QueryBuilder<PostModel>(
// Can use key if the query already exists.
queryKey: service.postKey(id),
builder: (context, state) {
final data = state.data;
if (state.error != null) return Text(state.error.toString());
if (data == null) return const SizedBox();
return Container(
margin: const EdgeInsets.all(10),
child: Column(
children: [
const Text(
"Title",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
Text(
data.title,
textAlign: TextAlign.center,
),
const Text(
"Body",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
Text(
data.body,
textAlign: TextAlign.center,
),
],
),
);
},
);
}
}