Skip to main content

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.