Migrating from 2.x to 3.x
Environment Requirements
Cached Query 3.0 requires Dart 3.0.0 or higher. Make sure to update your pubspec.yaml
:
environment:
sdk: ">=3.0.0 <4.0.0"
Query State
All query, infinite query and mutation states have been changed to sealed classes to take advantage of dart 3 pattern matching.
There is no longer a query status enum. There is now a matching sealed class, for exaple the query sealed class is:
class QueryInitial<T> extends QueryStatus<T> {}
class QueryLoading<T> extends QueryStatus<T> {}
class QuerySuccess<T> extends QueryStatus<T> {}
class QueryError<T> extends QueryStatus<T> {}
This change allows for more specific information to be passed with each state, such as the reason for the loading state or the data key being non-nullable in the success state.
For convenience you can check the current status of a query using:
final allStatuses = state.isLoading || state.isInitial || state.isError || state.isSuccess;
Example
- Before
- After
- Using Switch
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,
),
],
),
);
},
);
}
}
class Post extends StatelessWidget {
final int id;
final bool enabled;
const Post({Key? key, required this.id, required this.enabled})
: super(key: key);
Widget build(BuildContext context) {
return QueryBuilder<QueryState<PostModel>>(
enabled: enabled,
// Can use key if the query already exists.
queryKey: service.postKey(id),
builder: (context, state) {
final data = state.data;
if (state case QueryError(:final error)) return Text(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,
),
],
),
);
},
);
}
}
class Post extends StatelessWidget {
final int id;
final bool enabled;
const Post({super.key, required this.id, required this.enabled});
Widget build(BuildContext context) {
return QueryBuilder<QueryStatus<PostModel>>(
enabled: enabled,
// Can use key if the query already exists.
queryKey: service.postKey(id),
builder: (context, state) {
return switch (state) {
QueryError<PostModel>() =>
Text((state as QueryError).error.toString()),
QueryStatus<PostModel>(:final data) when data != null => 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,
),
],
),
),
_ => const SizedBox(),
};
},
);
}
}
The base class for each cachable item is:
- QueryStatus - for single queries.
- InfiniteQueryStatus - for infinite queries.
- MutationState - for mutations
Configuration Changes
The configuration system has been updated with separate global and query-specific config objects.
Classes are now:
GlobalQueryConfigFlutter
- Global config for all queries, infinite queries and mutations. If using Cached Query Flutter.QueryConfigFlutter
- Query specific config for queries and infinite queries. If using Cached Query Flutter.GlobalQueryConfig
- Global config for all queries, infinite queries and mutations. If using Cached Query (non-Flutter).QueryConfig
- Query specific config for queries and infinite queries. If using Cached Query (non-Flutter).
The local config will still inherit from the global config which is the same as before.
- Before
- After
void main() {
CachedQuery.instance.configFlutter(
config: QueryConfigFlutter(
refetchDuration: Duration(seconds: 4),
),
storage: await CachedStorage.ensureInitialized(),
);
runApp(MyApp());
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
CachedQuery.instance.configFlutter(
config: GlobalQueryConfigFlutter(
refetchOnResume: true,
),
storage: await CachedStorage.ensureInitialized(),
observers: [
Observer(),
QueryLoggingObserver(colors: !Platform.isIOS),
],
);
runApp(const MyApp());
}
Multiple observers can now be passed to the global config.
Data Type Changes
Query success state data is now the type of the generic, usually this is a non-null type. If you need nullable data, explicitly pass T?
as the generic type.
- Before
- After
QueryBuilder<PostModel>(
queryKey: ['post', id],
queryFn: () => api.getPost(id),
builder: (context, state) {
final data = state.data; // data is PostModel?
if (data == null) return const SizedBox();
return Text(data.title);
},
)
QueryBuilder<PostModel>(
queryKey: ['post', id],
queryFn: () => api.getPost(id),
builder: (context, state) {
return switch (state) {
QuerySuccess<PostModel>(:final data) => Text(data.title), // data is PostModel (non-nullable)
QueryError<PostModel>() => Text('Error: ${state.error}'),
_ => const SizedBox(),
};
},
)
For nullable data, use:
Query<PostModel?>(
...
)
Query Interface and Observers
The base class for queries is now Cachable<T>
. This has changed from QueryBase<T>
.
class Observer extends QueryObserver {
void onChange(
Cacheable<dynamic> query,
QueryState<dynamic> nextState,
) {
// Do something when changing
super.onChange(query, nextState);
}
}
Mutation
The queryFn
parameter has been renamed to mutationFn
in the Mutation
class.