utorok 27. septembra 2016

Android - Product Flavors

Od nástupu Android Studia bol predstavený aj nový buildovací systém Gradle. Tento systém je open source, flexibilný, postavený na groovy jazyku, v mnohom uľahčuje prácu vývojárovi. 
Ako android vývojár som sa stretol s tým, že aplikácia mala byť publikovaná na dva odlišné obchody s použitím mierne odlišných funkcií špecifických pre daný obchod. Typické funkcie špecifické pre dané obchody bývajú nákupy v aplikácii alebo prostredníctvom reklamy. Tých obchodov, kde môžeme publikovať aplikácie je viacero, spomeniem ich zopár: 
  • Google play - najznámejší, keďže sa jedná o Android systém. 
  • Amazon App Store - Amazon vytvoril vlastnú variantu Android Systému pod názvom Fire OS. 
  • Samsung Galaxy Apps - obchod od Samsungu, čo viac k tomu dodať. 
S publikovaným aplikácie na dva alebo viacej obchodov nie je problém. Problém nastáva pri tom, keby sme chceli použiť v aplikácii špecifické funkcie pre daný store ako nákupy v aplikácií. Napr. Amazon nepodporuje Google Play Services v ich systéme Fire OS (podporuje In-app Purchases), čo znamená, že nevieme robiť nákupy v Google obchode (ten má zase In-app Billing). Tento problém sa dá riešiť viacerými spôsobmi. Jeden spôsob je ten, že budem mať dva odlišné projekty v Android Studiu jednej aplikácie, väčšinu kódu budú mať oba projekty identickú, budú sa odlišovať len v implementáciach nákupov. Ak nájdem chybu v kóde, ktorý je rovnaký pre oba projekty, tak to znamená aj dve rovnaké opravy kódu v dvoch projektoch. Tiež prepínanie sa medzi projektami počas vývoja nie je najrýchlejšie.

Druhý spôsob bude taký, že budem mať jeden projekt a nakonfigurujem v buildovacom systéme gradle product flavors. Tento spôsob je elegantnejší, jeden kód, ktorý je rovnaký(zdielaný) pre obe verzie aplikácie, bude len raz a pridáme implementácie funkcií pre konkrétne obchody oddelene od zdieľaného kódu. Bližšie o build flavors sa dá dočítať na vývojarských stránkach Android: Product Flavors and Variants. Vysvetlím použitie druhého spôsobu na príklade. Majme projekt, ktorý má následujúcu štruktúru, viď. následujúci obrázok:

Android Studio - štruktúra projektu

Začneme tým že pridáme niekoľko riadkov do súboru build.gradle, ktorý sa nachádza v aplikačnom module (app).

productFlavors{
         google{
            versionCode 1
            versionName '1.1'
         }
          amazon{
            versionCode 2
            versionName '1.2'
          }
}


Po vložení hore uvedených riadkov a zosynchronizovaní build.gradle nám vytvorí niekoľko variant a to amazonDebug, amazonRelease, googleDebug, googleRelease, ako vidieť na obrázku nižšie.

Android Studio - Build Variants
Pred synchronizáciuo sme mali len dve build varianty a to Debug a Release. Keby sme pridali ďalšiu product flavors, tak Android Studio nám vytvorí 6 build varianta, ktorá bude mať vypadať následujúco:
<meno>Debug 
<meno>Release

V Android Studiu sa vieme teraz prepínať medzi build variantami. Momentálne máme len build varianty vytvorené, keď chceme ešte doplniť kódy špecifické pre každú variantu potrebujeme vytvoriť adresárovú štrukúru zdrojových kódov v Android Studiu. Štruktúra adresárov teraz bude vypadať následujúco:

Android Studio - Štruktúra projektov po pridaní product flavors

Túto štruktúro nám Android Studiu nevytvorí, tú si musíme vytvoriť sami. Štruktúra pre zdrojové kódy je podľa následujúceho vzorca:

src\<productFlavors-name>\java\<package>\SpecifickaImplementaciaPodlaFlavoru.java

Pre Resources je štruktúra jednoduchšia:

src\res\layout\LayoutSpecifickyPreProductFlavor.xml

V Adresáry src\main sú umiestnené všetky zdrojové kódy, ktoré nie sú špecifické pre určitú product flavor. Ako príkladom ukážem aplikáciu, ktorá bude robiť dve veci, záleži od jej product flavor. Amazon flavor bude robiť rozdiel dvoch čísel, bude mať aj rozdielne pozadie activity oproti google flavor. Zatiaľ čo google flavor bude dve čísla spočítavať. V adresári src\main je trieda Calculating.java, ktorá je spoločná pre obe flavors, jej zdrojovy kód je následujúci:

package test.vuforia.goodrequest.com.gradletest;

public class Calculating {

     public static int Add(int number1, int number2) {
         return number1 + number2;
     }
     public static int Substraction(int number1, int number2) {
         return number1 - number2;
     }
}

Layout pre google flavor je následujúci:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent" 
android:orientation="vertical" 
android:background="@color/black">

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="Cislo 1" 
android:textColor="@color/white"/> 

<EditText android:id="@+id/number1"
 android:layout_width="match_parent"
 android:layout_height="wrap_content" 
 android:background="@color/white" 
 android:inputType="number"/>

<TextView android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textColor="@color/white" 
 android:text="Cislo 2"/>

 <EditText android:id="@+id/number2" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content"
 android:background="@color/white"
 android:inputType="number"/>

 <Button android:id="@+id/sucet"
 android:layout_height="wrap_content"
 android:layout_width="wrap_content" 
 android:text="Sucet" /> 

<TextView android:id="@+id/vysledok"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textColor="@color/white"
 android:textStyle="bold"/>

</LinearLayout>

Layout pre Amazon flavor je rovnaká, rozdiel je len v použitých farbách pozadia LinearLayoutu a Button má iný textový popisok a id. Pod popiskom myslím android:text attribut. Trieda MainActivity.java má následujúci kód:

package test.vuforia.goodrequest.com.gradletest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
private Button sucet;
private EditText cislo1, cislo2;
private TextView vysledok;
@Override
protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      cislo1 = (EditText) findViewById(R.id.number1);
      cislo2 = (EditText) findViewById(R.id.number2);
      vysledok = (TextView) findViewById(R.id.vysledok);
      sucet = (Button) findViewById(R.id.sucet);
      sucet.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
               if ((cislo1.getText().length() != 0) && (cislo2.getText().length() != 0)) {
                     int scitanec1 = Integer.valueOf(cislo1.getText().toString());
                     int scitanec2 = Integer.valueOf(cislo2.getText().toString());
                     vysledok.setText("" + Calculating.Add(scitanec1, scitanec2));
                }
           }
      });
}
}


V podstate aj amazon flavor je velmi podobný, odlišuje sa len v implementácii OnClick listeneru buttonu: 

rozdiel.setOnClickListener(new View.OnClickListener() {
     @Override 
      public void onClick(View v) {
            if ((cislo1.getText().length() != 0) && (cislo2.getText().length() !=0)){
                  int mensenec = Integer.valueOf(cislo1.getText().toString());
                  int mensitel = Integer.valueOf(cislo2.getText().toString());
                  vysledok.setText("" + Calculating.Substraction(mensenec,mensitel));
           }
     }
});

Takýmto flexibilným spôsobom vieme vytvoriť viacero verzií jednej aplikácie rozlišujúcej sa vo vzhľade alebo vo funkcii. Nepotrebujeme k tomu žiadne dva projekty, stačí nám jeden. Pre úplnosť uvádzam výpis build.gradle súboru nachádzajúceho sa v app module: 

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        applicationId "test.vuforia.goodrequest.com.gradletest"
        minSdkVersion 18
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    productFlavors{
        google{
            versionCode 1
            versionName '1.1'
        }
        amazon{
            versionCode 2
            versionName '1.2'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
}

Text je publikovaný aj na blogu som písal aj pre spoločnosť GoodRequest s.r.o., kde som v dobe písania pôsobil.

Žiadne komentáre:

Zverejnenie komentára