write once, ship multiple times

72
Write once, ship multiple times ŽELJKO PLESAC

Upload: zeljko-plesac

Post on 14-Apr-2017

128 views

Category:

Software


0 download

TRANSCRIPT

Write once, ship multiple times

ŽELJKO PLESAC

Independent design & development agency

107 people in 3 offices

18 Android engineers

WHITE LABEL SYSTEMS

A white label product is a product or service produced by one company (the producer) that other companies (the marketers) rebrand to make it appear as if they had made it.

- WIKIPEDIA

Provides your brand with a refined product.

Outsourcing the development to a trusted thirty-party company.

Saving time and money.

Lack of control.

Lack of customisation.

Unified product vision.

HOW TO BUILD WHITE LABEL APPS?

NO.

Carefully designing the system.

PREREQUISITES

THOU SHALL NOT USE FAT CLIENTS.

Fat clients

• not customisable on the fly

• new app version for each new

feature

• prone to errors

• hard to maintain

MOST OF THE SYSTEMS FAIL ON THIS POINT.

SYSTEM ARCHITECTURE

Flavours.

Modules.

FLAVOUR ARCHITECTURE

Main flavour.

Product flavours.

MAIN FLAVOUR

• default product

• used only for demo purposes

• contains shared code

• uses default set of resources

• can be customised by configuration file

FLAVOURS

• each flavour is an application

• holds product specific resources

• contains only product specific code

• should be as minimal as possible

ADVANTAGES

Easy to maintain and develop.

Easy to configure.

Resource and code sharing between main flavour and product flavour.

DISADVANTAGES

Customisation is extremely hard.

Exponential increase of files and resources.

MODULE ARCHITECTURE

Main module.

Product module.

MAIN MODULE

• set of core functionalities

• default resources

• can be configured by configuration file

PRODUCT MODULES

• each module is one app

• product specific code and resources

ADVANTAGES

Easy to customise.

Apps are isolated.

Business logic can be product specific.

DISADVANTAGES

Hard to maintain.

Losing focus.

CUSTOMIZE APP BEHAVIOUR

BUILD CONFIG FILE

Customise constant values between the apps.

productFlavors {

white { dimension ‘product’ applicationId ‘com.infinum.white’ buildConfigField STRING, GOOGLE_ANALYTICS_ID, ‘”analyticsIdForWhite”’ buildConfigField STRING, API_URL, ‘”www.api.co/white/v1”’ manifestPlaceholders = [urlScheme: “white”] }

blue { dimension ‘product’ applicationId ‘com.infinum.blue’ buildConfigField STRING, GOOGLE_ANALYTICS_ID, ‘”analyticsIdForBlue”’ buildConfigField STRING, API_URL, ‘”www.api.co/blue/v1”’ manifestPlaceholders = [urlScheme: “blue”] } }

HostModule.setEndpoint(BuildConfig.API_URL);

AnalyticsModule.setGoogleAnalyticsId(BuildConfig. GOOGLE_ANALYTICS_ID);

BONUS - CUSTOMISE RESOURCES

applicationVariants.all { resValue XML_STRING, SEARCH_AUTHORITY, applicationId + '.providers.SearchSuggestionsProvider'}

CONFIGURATION FILE

Locally cached or obtained from the API.

More flexible that BuildConfig file.

Descriptive method naming.

CUSTOMISE APPLICATION ON THE FLY

• enable/disable features

• data flow

• screen flow

public interface AppConfig {

boolean isSocialLoginEnabled();

List<MenuItem> getLoggedInUserNavigationMenu(); }

public class WhiteConfiguration implements AppConfig {

private static final List<MenuItem> LOGGED_IN_USER_NAVIGATION_MENU = Collections.unmodifiableList(Arrays.asList( new MenuItem(R.drawable.ic_gamepad, R.string.home), new MenuItem(R.drawable.ic_settings, R.string.settings), new MenuItem(R.drawable.ic_settings, R.string.info) ));

@Override public boolean isSocialLoginEnabled() { return !BuildConfig.DEBUG || BuildConfig.APPLICATION_ID.endsWith(STAGING_APP_ID); }

@Override public List<MenuItem> getLoggedInUserNavigationMenu() { return LOGGED_IN_USER_NAVIGATION_MENU; } }

DESCRIPTIVE METHODS

Method names should be descriptive and should not contain product names.

public interface AppConfig {

boolean isWhite();

boolean isBlue(); }

PROGRAM TO INTERFACES

INTERFACES

Extract functionalities.

Integrate with external dependencies.

Default or product specific implementation.

public interface ImageLoader {

void displayImage(ImageView imageView, @NonNull String imageUrl);

void displayImage(ImageView imageView, @NonNull File imageFile);

}

@Module public class ProvidersModule {

@Provides public ImageLoader provideImageLoader(ImageLoaderType type) { switch(type){ case GLIDE: return new GlideImageLoader(); default: return new PicassoImageLoader(); } }

provided with

configuration file

Include external dependencies only for specific product types.

whiteCompile 'com.github.bumptech.glide:glide:3.6.1' blueCompile 'com.github.bumptech.glide:glide:3.5.0’

GROUP FUNCTIONALITIES IN FEATURES

Features

Each functionality is a feature.

Can be enabled/disabled.

Can be customised.

Feature

Contains everything what’s needed

for their integration.

Opened or closed.

CUSTOMISATION EXAMPLES

Default resources + product specific resources.

Default resources + product specific resources.

Same key is needed.

Provide presenter implementation per product.

Provide presenter implementation per product.

DI has to be handled per product (no default option).

Interfaces, features and configuration.

Interfaces, features and configuration.

Provide default and custom configuration.

Interfaces, features and configuration.

Provide default and custom configuration.

WHAT?

Feature has a default navigation flow, but it has to be customised only for one

or two products.

public interface Navigation {

<T> void createTrip(FragmentActivity activity, int createTripRequestCode, Map<String, T> params);

}

public interface NavigationBehaviour {

<T> void startCreateTripScreen(FragmentActivity activity, int createTripRequestCode, Map<String, T> params); }

public class NavigationManager implements Navigation {

private NavigationBehaviour navigationBehaviour;

public NavigationManager(NavigationBehaviour navigationBehaviour) { this.navigationBehaviour = navigationBehaviour; }

@Override public <T> void createTrip(FragmentActivity activity, int createTripRequestCode, Map<String, T> params) { navigationBehaviour.startCreateTripScreen(activity, createTripRequestCode, params);

} }

public class DefaultNavigationBehaviour implements NavigationBehaviour {

@Override public <Parcelable> void startCreateTripScreen(FragmentActivity activity, int createTripRequestCode, Map<String, Parcelable> params) {

Intent intent = CreateTripActivity.newIntent(activity, TripType.WALKING, recentStops); activity.startActivityForResult(intent, createTripRequestCode);

} }

public class BlueNavigationBehaviour implements NavigationBehaviour {

@Override public <Parcelable> void startCreateTripScreen(FragmentActivity activity, int createTripRequestCode, Map<String, Parcelable> params) {

Intent intent = PlanTripActivity.newIntent(activity, TripType.CAR, recentStops); activity.startActivityForResult(intent, createTripRequestCode);

} }

CUSTOMIZE EVERYTHING?

When too much customisation is needed.

Different product vision.

FLAVOUR MODULESTAND ALONE APPLICATION

ROUNDUP

Thin clients.

Architecture planning.

Offer limited customisation.

Leave the system.

Visit infinum.co or find us on social networks:

infinum.co infinumco infinumco infinum

Thank you! [email protected] @ZELJKOPLESAC