Add multiple targets in Flutter apps (VS Code & Android Studio)

  • 2019-04-06 01:33 AM
  • 158

In this article you will learn how to setup separate targets for Dev, Staging and Production Environments in Flutter and add these configurations to IDEs (VS Code and Android Studio) to toggle between these targets with EASE.

Do you think only smart OR _hard_work pays off?

I think the hard and smart work balance will drive you to the success possibly!!

I am working on my self now a days to improve lifestyle by improving my daily habits. Let me share some of my expirements.

Do you know Pomodoro Technique?

The Pomodoro Technique is a time management methoddeveloped by Francesco Cirillo in the late 1980s. The techniqueuses a timer to break down work into intervals, traditionally 25 minutes in length, separated by short breaks.

I am trying to follow Pomodoro at my work place, it’s hard to follow while you start, because every break time you are in between a task of finished task early than break time, but the result is way productive. While you know you will have to take a break, your focus more on your work, and in a break you will have enough time to think the problems and road map to finish it.

While we learn to driving, you may notice after a break your driving is seems improved. This is how Pomodoro Technique work.

It’s comparatively easy to get rank in top 10 than be a topper in class!

Yes that’s true and everyone know if you are not a last bencher and last night reader like me 😉. That’s why back in 1895 Vilfredo Pareto introduce 80-20 rule.

Roughly 80% of the effects come from 20% of the causes.

Let’s see how it applies to Software and Sports as these are of my interest fields.

In Software:- Microsoft noted that by fixing the top 20% of the most-reported bugs, 80% of the related errors and crashes in a given system would be eliminated.

In Sports:- Roughly 20% of the exercises and habits have 80% of the impact and the trainee should not focus so much on a varied training.

Enough motivation, but this is directly relate to topic only. lets dig in to the topic!

Overview

Why we need multiple targets? How to make it?

As a developer, we have to deal with multiple environments as those required to separate testing audience and real audience. We need to set separate configurations like base url, configuration keys, app names, app icons, databases…. for different target audience. Most of products follow two environments development and production, but some of thme using staging and QA in between development and production to ensure the expected outcome. So common used environments are Development, {Staging, QA}, Production.

I was wondering how I will manage environment variable in Flutter like i was used to do in iOS. Then I found this stakoverflow question answered by Seth Ladd(who is a Product Manager at Google, currently in flutter team). I found this blog too, which is for Android Studio only, so I decided to write one for VS Code(I am using VS Code).

My personal favourite Flutter IDE is VS Code, following are my personal preferences:

  • I am full-time iOS Developer 😎😉, so I don’t mind to fresh start with VS Code
  • VS Code is fast and lightweight (smooth operator), less memory usage and battery life
  • I love the blue Theme of VS Code, later I customised my Xcode them to Blue, same like VS Code
  • It let me focus, more responsive and not sluggish

Android Studio Flutter ready Setup

If you are using VS Code from start and only opening the android folder in Android studio to debug native platform channel code Like me, then you have to set flutter SDK path first.

If you face following error, rather than set Dart SDK, set Flutter SDK will help.

Add multiple targets in Flutter apps

Dart SDK Not Found? Don’t panic!

Click on Android Studio on top left → Select Preferences… → Unfold Langues & Frameworks → Select Flutter → Select Flutter SDK Path → Click on Apply Button.

Add multiple targets in Flutter apps

Set Flutter SDK Path

Prerequisite

Create a new app and do following minor changes.

import 'package:flutter/material.dart';
import 'package:targety/home.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
main.dart is almost same as default tamplate
import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'This is a Production Build',
            ),
            Text(
              'Base url - https://myapp.com/api',
            ),
          ],
        ),
      ),
    );
  }
}

while home.dart is separated from main and slightly modified

TODO

Currently app support single environment, there is no way to set a different configurations for different environments(Except commenting/uncommenting code🤨). We will set three different environments Development, Staging, Production.

We will configure following things to separate each build from other,
1. Set app bar title with text App name — 
2. Set first label with description of the target
3. Set second label text with base url used for selected target
4. Set separate ThemeData for each target

Create shared configuration object

Lets create app_config.dart which will contains all environment related configurations.

AppConfig is a singleton class, so the single reference can be used throughout the app. We can also use AppConfig extends with InheritedWidget, but intentionally used shared instance to use in constant/enum classes in highly architectured projects.

import 'package:flutter/material.dart';

enum AppEnvironment {DEV, STAGE, PROD}

class AppConfig {
  // Singleton object
  static final AppConfig _singleton =
      new AppConfig._internal();

  factory AppConfig() {
    return _singleton;
  }

  AppConfig._internal();

  AppEnvironment appEnvironment;
  String appName;
  String description;
  String baseUrl;
  ThemeData themeData;

  // Set app configuration with single function
  void setAppConfig({AppEnvironment appEnvironment, String appName, String description, String baseUrl, ThemeData themeData}) {
    this.appEnvironment   = appEnvironment;
    this.appName          = appName;
    this.description      = description;
    this.baseUrl          = baseUrl;
    this.themeData        = themeData;
  }
}
AppConfig shared instance

Create main file for each environment

In flutter main.dart is entry point of app, so we can set app configuration in main.dart file. We want to create three different environments so will have to create three variant of main file , main_dev.dart, main_stage.dart, main.dart.

In each main file, we’ll set configurations in AppConfig class with the appropriate configuration data before runApp called. From then AppConfig can be used in entire app with access of shared instance.


import 'package:flutter/material.dart';
import 'package:targety/config/app_config.dart';
import 'package:targety/home.dart';

void main() {
  AppConfig().setAppConfig(
    appEnvironment: AppEnvironment.DEV,
    appName: 'Targety Dev',
    description: 'This is a Development version of Targety',
    baseUrl: 'https://dev-appname.com/api',
    themeData: ThemeData(
      primarySwatch: Colors.red,
      primaryColor: Colors.blueGrey,
    )
  );
  runApp(MyApp());
} 

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: AppConfig().appName,
      theme: AppConfig().themeData,
      home: MyHomePage(title: AppConfig().appName),
    );
  }
}

Likewise we have to set different configuration for different main file according to environment.

We just set configuration of shared AppConfig instance in main class. Instead of direct call runApp we derive function body and set configuration. Set title of MyApp and MyHomePage widget and set theme data as per AppConfig.

As we are using shared instance access of AppConfig properties will be same throughout the app as the reference is shared.

Run/Build using terminal

We can run the different variants by running flutter run with the --target or -t argument for short.

Run flutter help run in terminal to know about run parameters.

So, in our case:

  • to run the development build, we call flutter run -t lib/main_dev.dart
  • to run the staging build, we call flutter run -t lib/main_stage.dart
  • to run the production build, we call flutter run -t lib/main_prod.dart

To create a release build on iOS, we can run flutter build ios -t lib/main_<environment>.dart and we will get the correct IPA for our environment. To do a release build on Android, just replace ios with apk.

So far so good. But how handy it is to run command in terminal every-time? It’s very easy if we have an option to switch between environments in our respective IDEs.

Using VS Code

Follow these steps below to setup targets in VS Code

1. Press ⌘⇧D if you are not in debug tab yet. 
 2. Click on configuration beside play button. 
 3. Select add configuration… from dropdown
4. It will open a drop down in launch.json file
5. Search Flutter and select {}Flutter: Launch

Add multiple targets in Flutter apps

launch.json

6. Add “program”: “lib/main.dart” for if it’s not already exist there (default is main.dart if you don’t write program field)
7. Rename “name” with your app name
8. Copy that configuration and past two times for Dev and Staging 
9. Change name and program for just pasted configurations as per follow

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "<app_name>",
            "type": "dart",
            "request": "launch",
            "program": "lib/main.dart"
        },
        {
            "name": "<app_name>-Dev",
            "request": "launch",
            "type": "dart",
            "program": "lib/main_dev.dart"
        },
        {
            "name": "<app_name>-Stage",
            "request": "launch",
            "type": "dart",
            "program": "lib/main_stage.dart"
        }
    ]
}

Now toggle the targets from drop-down beside play button and test your configutaions for Development, Staging and Production.

Run it using fn + F5 or F5, hot reload will not push the change because we did change in launch.json and main.dart file.

Using Android Studio

  1. Open app folder in Android Studio (not just the android folder)
  2. Click on main.dart button left side of play button, will open a dropdown
  3. Click on Edit Configurations…
  4. Rename main.dart target to your app-name
  5. Click on + on top left corner to add new configuration
  6. Select Flutter from dropdown
  7. Edit name to -dev
  8. Check share checkbox
  9. Select Dart Entry point to main_dev.dart from lib folder
  10. Repeat steps 5–9 to add stage configuration

Now you can toggle between targets in Android Studio too. Toggle between targets and play with it.

Flutter multiple target demo source code is here.

Thanks for reading ❤

Follow me on Facebook | Twitter

Learn More

Learn Flutter & Dart to Build iOS & Android Apps
Flutter & Dart - The Complete Flutter App Development Course
Dart and Flutter: The Complete Developer’s Guide
Flutter - Advanced Course
Flutter for Business
Flutter for Developers
Flutter Tutorial for Beginners - Build Android and iOS Apps with a Flutter Framework
An Introduction to Flutter: The Basics
An introduction to Dart and Flutter

Originally published by Dinesh Kachhot at https://medium.com

Suggest