Connecting your data source
The template's Dart Frog API acts as an intermediary between your CMS and the client application, organizing your content into the blocks that form the basis of content organization within the app.
If you don't intend to write custom code to support the necessary block-organized endpoints from your CMS, you should create and deploy an API that uses the NewsDataSource
interface to collect and transform data.
Your implementation of the NewsDataSource
is called by the route handlers laid out in the api/routes
directory. The data source then requests data from your CMS and organizes it into the block-based data expected by the client before returning it to the route handler to be served to your client application. For more information about the structure and capabilities of the Dart Frog server that uses your data source, consult the Dart Frog documentation.
The NewsDataSource
class (api/lib/src/data/news_data_source.dart
) provides an interface that your data source must implement. Feel free to remove methods that provide data that you don't intend to use in the client app, or to add methods to provide data for functionality that you intend on adding to your app.
Creating a new data source
Begin by defining a new class that implements NewsDataSource
:
class YourCustomDataSource implements NewsDataSource
Your data source should have a means of interacting with your CMS as a field such as an HTTP or Dio client, and you might want to create separate named constructors if you have different CMS URLs for different flavors, such as development
and production
.
Implementing your data source
After creating your data source class, implement the methods defined in NewsDataSource
:
/// {@template news_data_source}
/// An interface for a news content data source.
/// {@endtemplate}
abstract class NewsDataSource {
/// {@macro news_data_source}
const NewsDataSource();
/// Returns a news [Article] for the provided article [id].
///
/// In addition, the contents can be paginated by supplying
/// [limit] and [offset].
///
/// * [limit] - The number of content blocks to return.
/// * [offset] - The (zero-based) offset of the first item
/// in the collection to return.
Future<Article?> getArticle({
required String id,
int limit = 20,
int offset = 0,
});
/// Returns a list of current popular topics.
Future<List<String>> getPopularTopics();
/// Returns a list of current relevant topics
/// based on the provided [term].
Future<List<String>> getRelevantTopics({required String term});
/// Returns a list of current popular article blocks.
Future<List<NewsBlock>> getPopularArticles();
/// Returns a list of relevant article blocks
/// based on the provided [term].
Future<List<NewsBlock>> getRelevantArticles({required String term});
/// Returns [RelatedArticles] for the provided article [id].
///
/// In addition, the contents can be paginated by supplying
/// [limit] and [offset].
///
/// * [limit] - The number of content blocks to return.
/// * [offset] - The (zero-based) offset of the first item
/// in the collection to return.
Future<RelatedArticles> getRelatedArticles({
required String id,
int limit = 20,
int offset = 0,
});
/// Returns a news [Feed] for the provided [category].
/// By default [Category.top] is used.
///
/// In addition, the feed can be paginated by supplying
/// [limit] and [offset].
///
/// * [limit] - The number of results to return.
/// * [offset] - The (zero-based) offset of the first item
/// in the collection to return.
Future<Feed> getFeed({
Category category = Category.top,
int limit = 20,
int offset = 0,
});
/// Returns a list of all available news categories.
Future<List<Category>> getCategories();
}
For example, an implementation of getArticle()
might look like:
Future<Article?> getArticle({
required String id,
int limit = 20,
int offset = 0,
bool preview = false,
}) async {
final uri = Uri.parse('$YOUR_CMS_BASE_URL/posts/$id');
final response = await httpClient.get(uri);
if (response.statusCode != HttpStatus.ok) {
throw YourAppApiFailureException(
body: response.body,
statusCode: response.statusCode,
);
}
final responseJson = response.jsonMap();
if (responseJson.isNotFound) return null;
final post = Post.fromJson(responseJson);
final article = post.toArticle();
return article;
}
The above example references a class not included in the template, Post
:
class Post {
const Post({
required this.id,
required this.date,
required this.link,
required this.title,
required this.content,
required this.author,
required this.image,
required this.category,
});
final int id;
final DateTime date;
final String link;
final String title;
final String content;
final Author author;
final String image;
final PostCategory category;
}
Since your CMS presumably doesn't respond with data in the block-based format used by the Article
class, you might want to define classes like Post
that mirror the data types and formats that your CMS returns.
You can use a package like json_serializable to generate code to create a Post
object from the JSON returned from your CMS (see JSON and serialization - Flutter Documentation).
You can then add an extension method such as toArticle()
on your Post
class that uses the relevant data from the Post
object and to create and return an Article
object that is served to your client app.
This structure of JSON -> Intermediary Object -> API Model
can be repeated when implementing any data source method, which receives data from your CMS that differs from what the method is expected to return.
Injecting your data source
After creating your data source, inject it into your API route handler through the Dart Frog middleware.
First, instantiate your data source:
final yourCustomDataSource = YourCustomDataSource();
Then, inject it through the middleware as a NewsDataSource
:
handler.use(provider<NewsDataSource>((_) => yourCustomDataSource));
As the template already contains a NewsDataSource
dependency injection, you can simply instantiate your data source and then replace inMemoryNewsDataSource
with yourCustomDataSource
.