Skip to main content

Flutter Starter App (Core) 🍎

This template is a Flutter starter application with VGV-opinionated best practices. It is the default template for the very_good create flutter_app command.

Very Good Core

Why Very Good Core?​

We liked the starter app provided by the flutter create command, but found ourselves making adjustments every time we started a project. To help streamline the process, we decided to create our own starter template with the core standards and best practices we use at Very Good Ventures. Similar to the Flutter Starter app, Very Good Core contains a basic counter app with some additional features for a more robust app foundation.

App Features βœ¨β€‹

  • Multi-Platform Support - Support for iOS, Android, Web, and Windows (macOS and Linux coming soon!)

  • Build Flavors - Multiple flavor support for development, staging, and production

  • Internationalization Support - Internationalization support using synthetic code generation to streamline the development process

  • Sound Null Safety - No more null-dereference exceptions at runtime. Develop with a sound, static type system.

  • Bloc - Layered architecture with bloc for scalable, testable code which offers a clear separation between business logic and presentation

  • Testing - Unit and widget tests with 100% line coverage (integration tests coming soon!)

  • Logging - Extensible logging to capture uncaught Dart and Flutter exceptions

  • Very Good Analysis - Lint rules for Dart and Flutter used internally at Very Good Ventures

  • Continuous Integration - Lint, format, test, and enforce code coverage using GitHub Actions

Getting Started πŸš€β€‹

info

In order to start using Very Good Core you must have the Flutter SDK installed on your machine.

Installation πŸ’»β€‹

For first time users, start by installing the Very Good CLI from pub.dev.

dart pub global activate very_good_cli

Create a new Flutter Project πŸ†•β€‹

Then, you can use the very_good create flutter_app command just like you would flutter create. If desired, can specify a custom org name at time of generation with the --org flag.

# Create a new Flutter app named my_app
very_good create flutter_app my_app --desc "My new Flutter app"

# Create a new Flutter app named my_app with a custom org
very_good create flutter_app my_app --desc "My new Flutter app" --org "com.custom.org"

Running the Project βš‘β€‹

Once you have finished running very_good create with the project directory of your choice, you can change directories into the new project directory and install the dependencies

cd my_app
flutter packages get

This project contains 3 flavors:

  • Development
  • Staging
  • Production

Each flavor has dedicated entry point (main_development.dart, main_staging.dart, main_production.dart) which can be used to setup, instantiate, and inject flavor-specific dependencies into the application.

For example:

  • In development we might want to output logs to the console but in staging and production we might want to upload logs to sentry.io or firebase analytics.
  • We might want to configure an ApiClient or DatabaseClient to point to a different endpoint for each flavor.

To run the desired flavor either use the launch configuration in VSCode or Android Studio, or use the following commands:

# Development
flutter run --flavor development --target lib/main_development.dart

# Staging
flutter run --flavor staging --target lib/main_staging.dart

# Production
flutter run --flavor production --target lib/main_production.dart
caution

Flavors are only supported on iOS, Android, Web, and Windows.

Now your app is running πŸŽ‰

Project Structure and Architecture πŸ—οΈβ€‹

Although Very Good Core is fairly basic in terms of functionality, the architecture and project structure is intended to scale from a simple hobby project to a large production ready application. A folder-by-feature project structure is used to maintain a modular project structure which helps the project scale as the number of features and/or developers increase.

In Very Good Core, there is only a single feature (counter) to start but that will quickly change as you build out your project. Each feature usually consists of a view and a cubit (or bloc). The view is responsible for holding the UI (Widgets) which the user sees and interacts with and the cubit/bloc is responsible for containing the business logic needed to manage the state of the feature.

For more details, read our best practices for building scalable apps.

Testing πŸ§ͺ​

Very Good Core ships with 100% code coverage.

note

To learn more about why we believe 100% code coverage is important and other testing best practices, read our guide to Flutter testing.

Running Tests πŸ§‘β€πŸ”¬β€‹

To run all unit and widget tests use the following command:

flutter test --coverage --test-randomize-ordering-seed random

To view the generated coverage report you can use lcov.

# Generate Coverage Report
genhtml coverage/lcov.info -o coverage/

# Open Coverage Report
open coverage/index.html

Working with Translations πŸŒβ€‹

This project relies on flutter_localizations and follows the official internationalization guide for Flutter.

Adding Strings​

  1. To add a new localizable string, open the app_en.arb file at lib/l10n/arb/app_en.arb.
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
  1. Then add a new key/value and description
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"helloWorld": "Hello World",
"@helloWorld": {
"description": "Hello World Text"
}
}
  1. Use the new string
import 'package:very_good_core/l10n/l10n.dart';


Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(l10n.helloWorld);
}

Adding Supported Locales​

Update the CFBundleLocalizations array in the Info.plist at ios/Runner/Info.plist to include the new locale.

    ...

<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>

...

Adding Translations​

  1. For each supported locale, add a new ARB file in lib/l10n/arb.
β”œβ”€β”€ l10n
β”‚ β”œβ”€β”€ arb
β”‚ β”‚ β”œβ”€β”€ app_en.arb
β”‚ β”‚ └── app_es.arb
  1. Add the translated strings to each .arb file:

app_en.arb

{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}

app_es.arb

{
"@@locale": "es",
"counterAppBarTitle": "Contador",
"@counterAppBarTitle": {
"description": "Texto mostrado en la AppBar de la pΓ‘gina del contador"
}
}

Continuous Integration πŸ€–β€‹

Very Good Core comes with a built-in GitHub Actions workflow but you can also add your preferred CI/CD solution.

Out of the box, on each pull request and push, the CI formats, lints, and tests the code. This ensures the code remains consistent and behaves correctly as you add functionality or make changes. The project uses Very Good Analysis for a strict set of analysis options used by our team. Code coverage is enforced using the Very Good Coverage GitHub Action.

Updating App Icons πŸ“±β€‹

When you create a new project, it has a default launcher icon. To customize this icon, you can do it by using the following steps for each platform.

Android​

  1. Review the Material Design product icons guidelines for icon design.

  2. In the [project]/android/app/src/main/res/ directory, place your icon files in folders named using configuration qualifiers. The default mipmap- folders demonstrate the correct naming convention.

  3. In AndroidManifest.xml, update the application tag’s android:icon attribute to reference icons from the previous step (for example, <application android:icon="@mipmap/ic_launcher" ...).

  4. To verify that the icon has been replaced, run your app and inspect the app icon in the Launcher.

iOS​

  1. Review the iOS app icons guidelines.

  2. In the Xcode project navigator, select Assets.xcassets in the Runner folder. Update the placeholder icons with your own app icons.

  3. Verify the icon has been replaced by running your app using flutter run.