Unsplash
The Unsplash feature owns photo browsing, search, detail screens, state, and API-client integration in one Flutter package. Its descriptor contributes the routes and initializes the services needed by those screens.
Capabilities
This feature includes most of the capabilities that you might expect in the Unsplash app:
- Seeing the latest and featured photos
- Searching photos
- Seeing the details of photos
- Exploring the other information associated with a photo
- Built-in navigation between the listing and the details
- Using the Unsplash Client from pub.dev to invoke all server calls
How the pieces fit together
The FeatureDescriptor
The entry point for the Unsplash feature is a straightforward FeatureDescriptor. It sets up metadata, initializes the Unsplash API client with keys from environment variables, and configures routes:
final feature = FeatureDescriptor(
name: 'unsplash',
title: 'Unsplash',
description: 'View photos, collections and users on Unsplash',
icon: Icons.photo_camera,
init: () async {
await dotenv.load(fileName: '.env');
final accessKey = dotenv.get('UNSPLASH_ACCESS_KEY');
final secretKey = dotenv.get('UNSPLASH_SECRET_KEY');
vyuh.di.register(
UnsplashStore(accessKey: accessKey, secretKey: secretKey),
);
},
routes: routes,
);Metadata Matters
Although it might seem unnecessary, take your time to specify good metadata for a feature. This helps identify the feature in the developer tools and makes it easier for your team members to understand what the feature contains.
How the pieces fit together
Navigation and Routes
How the pieces fit together
The navigation inside the Unsplash feature consists of two tab routes: Home and Search.
The Home tab shows listings of photos under various categories (Latest, Featured, Collections, Topics). Tapping on a photo navigates to photo details. The Search tab has a similar pattern with search results leading to photo details.
The route setup uses StatefulShellRoute with two tabs:
List<RouteBase> routes() {
return [
StatefulShellRoute.indexedStack(
branches: [
StatefulShellBranch(routes: [
GoRoute(
path: '/unsplash/home',
builder: (context, state) => const UnsplashHome(),
routes: [
GoRoute(path: 'photos/:id', builder: ...),
GoRoute(path: 'collections/:id', builder: ...),
GoRoute(path: 'topics/:id', builder: ...),
],
),
]),
StatefulShellBranch(routes: [
GoRoute(
path: '/unsplash/search',
builder: (_, __) => const SearchView(),
),
]),
],
),
];
}Separation of Concerns
How the pieces fit together
The feature follows a clean separation between three actors:
- The Unsplash Store -- Makes the API calls to the Unsplash client and fetches details. Uses the API key set up in the
init()method of theFeatureDescriptor. - The API Client -- Responsible for making server calls via the
unsplash_clientpub package. - The Widgets -- The rendering surface for information fetched from API calls and serviced through the store. Widgets always talk to the store and never have direct access to the API client.
Widgets
The three core widgets forming a mini design system include:
- PhotoCard -- Appears everywhere there is a list of photos
- CollectionView -- Shows a list of photos in a grid layout
- PhotoDetail -- Displays full details of a selected photo
How the pieces fit together
The Home tab uses an enumeration-based approach for its sub-tabs (Latest, Featured, Collections, Topics), simplifying widget creation per sub-tab and improving code readability.
How the pieces fit together
Summary
The feature uses regular Flutter widgets and hot reload. Its store owns API requests and observable state, while its descriptor exposes routes and setup to the app.
Additionally, the use of the Vyuh Framework gives you modularity: the ability to neatly tuck all feature-related functionality into a separate package.
Read the Code
See the code for the example on GitHub: Unsplash