Un guide système pour créer des applications Android en marque blanche

Comment écrire un code une fois et vendre 20 applications mobiles? Nous avons trouvé la réponse à travers des essais et des faux et décomposé l'expérience en points: à partir de l'article, vous apprendrez comment implémenter sans douleur un projet android en marque blanche.





Salutations et salutations! Mon nom est Kirill, au travail, j'ai eu une fois une tâche intéressante pour développer une application Android White Label. J'ai étudié les réalisations de collègues dans ce domaine et n'ai trouvé que:





  • (, , , etc) , ;





  • , (, , etc).





, , , best practices. .





1

« » 10 eCommerce retail. , , : .





( ), : , .





... ... ! White Label ? : , – .





:





1.1

, . : .





, SEPHORA , «» . :





, . , :





? .





, , «» — White Label , . , — :)





1.2

: , .





  1. :





    • – ;





    • – , , ;





    • ...





  2. :





    • , ;





    • , : , , .





  • ;





  • – ;





  • 10 100 .





1.3 ? ? White Label?

– . , « ». , . :





  1. /:





    • ;





    • , AppGyver – drag’n’drop , ( , );





    • – .





  2. White Label:





    • , ;





    • (, )





. , White Label. «white label android development» , .





2

White Label

«» «» ( , ). , Clean Architecture…





… :





  1. ?





  2. ?





  3. ?





  4. ?





, , !





2.1

– , 100 . – Gradle Product Flavors.





Gradle Product Flavors, . White Label:





, «» . , main



.





. , .





. 100, . , .





, , : , .





flavors. , :





  1. «» — ;





  2. «» — .





flavors — loyaka



jewelry



. best practice — flavor . ? .





:





  1. project_flavors



    ;





  2. — gradle- flavor_loyaka.gradle



    , flavor_jewelry.gradle



    flavors_common.gradle



    ;





  3. build.gradle



    app



    .





.





flavor_loyaka.gradle





apply from: "$rootDir/project_flavors/flavors_common.gradle"  
android {
    productFlavors {

        loyaka {
            dimension APP_DIMENSION

            resValue "string", APP_NAME_VAR, ''
            applicationId BASE_PACKAGE + 'loyaka'
        }
    }
}
      
      



flavor_jewelry.gradle





apply from: "$rootDir/project_flavors/flavors_common.gradle"  
android {
    productFlavors {

        jewerly {
            dimension APP_DIMENSION

            resValue "string", APP_NAME_VAR, ''
            applicationId BASE_PACKAGE + 'jewelry'
        }
    }
}
      
      



flavors_common.gradle





android {
    ext.DIMENSION_APP = "app"
    ext.APP_NAME_VAR = "app_name"
    ext.BASE_PACKAGE = "com.livetyping."
}
      
      



, flavors — build.gradle app



:





...
apply from: "$rootDir/project_flavors/flavor_loyaka.gradle"
apply from: "$rootDir/project_flavors/flavor_jewelry.gradle"
apply from: "$rootDir/project_flavors/flavors_common.gradle"

android {
    ...
    flavorDimensions APP_DIMENSION
}
...
      
      



2.2

2.2.1

, :





  • ;





  • , , ;





  • (, . ).





flavors . 3 :





  1. main



    ;





  2. gradle main



    flavor;





  3. flavor . , main/res



    , loyaka



    loyaka/res



    ;





, main/res



loyaka/res



animal.webp



? , , Gradle . , :





Gauche - ressources par saveur;  sur la droite se trouve le fichier APK final.
— flavor; — APK.

! main



, flavor .





2.2.2 Best practices

:





  • — ;





  • — colors.xml



    flavor .





, , , . , , . — primary



accent



. , .





, 100 ! , , . , : , — , .





, , «» .





2.2.3

project_styleguide.xml



:





  • «» — loyaka/res/values/project_styleguide.xml:





<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="active">#68b881</color>
    <color name="background">#36363f</color>
    <color name="disabled">#daede0</color>
    <color name="field_dark">#f5f5f5</color>
    ...
</resources
      
      



  • «» — jewelry/res/values/project_styleguide.xml:





<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="active">#a160d5</color>
    <color name="background">#f6ebff</color>
    <color name="disabled">#e2c8f6</color>
    <color name="field_dark">#f5f5f5</color>
    ...
</resources>
      
      



2.3

2.3.1

:





  1. ;





  2. .





, . : . .





«--»:





  1. :





    • ;





    • ;





    • …





  2. :





    • : email;





    • .





  3. :





    • -: EAN-8, EAN-13, CODE-128.





  4. …





2.3.2

? :





  1. – «» , «» ( , DSL);





  2. – , .





:





  1. Gradle buildConfigField







    • gradle ;





    • java BuildConfig



      , .





  2. JSON





    • json ;





    • , .





.





2.3.3 â„–1. Gradle buildConfigField

:





  • — DSL : ; ;





  • — ;





  • — BuildConfig



    .





— : , .





DSL:





buildConfigField MAIN_SCREEN_TYPE, MAIN_SCREEN_VAR, MS_SHOPS







2.3.4 â„–2. JSON

:





  • — HOCON;





  • — DSL JSON Schema, ;





  • — iOS Android.





:





  • — ;





  • — JSON Schema .





2.3.5 ?

, . Gradle. , JSON + Schema . — , , . .





Gradle, . , JSON Schema – .





2.3.6 Best practices buildConfigField

buildConfigField



, «» :





  1. Enum



    , , ;





  2. Find & Replace .





: DSL . , . gradle- . «--». business_rules



.





: loyalty_business_rules.gradle:





/*_______________ENTER USER ID________________*/

/*________User ID________*/

/*__Variable__*/
ext.USER_ID_VAR = "USER_ID"
ext.USER_ID_TYPE = "com.example.whitelabelexample.domain.models.UserIdType"

/*__Values__*/
ext.UI_PHONE = USER_ID_TYPE + ".PHONE"
ext.UI_EMAIL = USER_ID_TYPE + ".EMAIL"

/*_______________NO CARD________________*/

/*________Obtain card methods________*/

/*__Variable__*/
ext.OBTAIN_METHODS_VAR = "OBTAIN_CARD_METHODS"
ext.OBTAIN_METHODS_ENUM = "com.example.whitelabelexample.domain.models.ObtainCardMethod"
ext.OBTAIN_METHODS_TYPE = "java.util.List<" + OBTAIN_METHODS_ENUM + ">"

/*__Optional values__*/
ext.OM_GENERATE = OBTAIN_METHODS_ENUM + ".GENERATE_VIRTUAL"
ext.OM_BIND = OBTAIN_METHODS_ENUM + " .BIND_PHYSICAL"

...

      
      



UI_PHONE



— UI_



? UserId



: , .





flavor, .





: flavor_loyaka.gradle:





...

loyaka {
    ...

    /* MAIN SCREEN */
    buildConfigField MAIN_SCREEN_TYPE, MAIN_SCREEN_VAR, MS_CARD

    /* MODULES */
    buildConfigField APP_MODULES_TYPE, APP_MODULES_VAR, list(AM_LOYALTY, AM_SHOWCASE)

    /* REGISTRATION */
    buildConfigField USER_ID_TYPE, USER_ID_VAR, UI_EMAIL

    ...
}

      
      



flavor_jewelry.gradle:





...

jewelry {
    ...

    /* MAIN SCREEN */
    buildConfigField MAIN_SCREEN_TYPE, MAIN_SCREEN_VAR, MS_SHOPS

    /* MODULES */
    buildConfigField APP_MODULES_TYPE, APP_MODULES_VAR, list(AM_LOYALTY, AM_SHOPS)

    /* REGISTRATION */
    buildConfigField USER_ID_TYPE, USER_ID_VAR, UI_PHONE

    ...
}

      
      



2.3.7

Clean Architecture.





data



, . ui



domain



.





? , . , , — , . 2 .





BuildConfig



, JSON



. , (). use case . , :





  1. — , , ;





  2. — , , .





: BuildCardConfig.kt:





class BuildCardConfig : CardConfig {

    override fun numberMask(): String = BuildConfig.CARD_NUMBER_MASK

    override fun barcodeType(): BarcodeType = BuildConfig.BARCODE_TYPE

    override fun obtainmentMethods(): List<ObtainCardMethod> = BuildConfig.OBTAIN_CARD_METHODS

    ...
}

      
      



( UML; ui



MVVM):





UseCase



, . — , UseCase



.





2.3.8

« domain



? !» . , — . , 2 :





  1. ;





  2. .





« » , , «» . Gradle JSON Schema — domain



.





, GetMainTabUseCase.kt:





class GetMainTabUseCase(
    private val mainConfig: MainConfig
) {

    operator fun invoke(): NavigationTab {
        val mainTab = mainConfig.mainTab()
        val mainModule = tabsByModules.entries.find { it.value == mainTab }!!.key
        val isModuleEnabled = BuildConfig.APP_MODULES.contains(mainModule)
        if (isModuleEnabled.not()) {
            throw IllegalStateException("Can't use a tab ($mainTab) as main, it's module is disabled  — fix config!")
        }
        return mainTab
    }
}

      
      



: UseCase



, . .





— UseCase



, : ui



Config



UseCase



. , , , .





, , . - .





2.4

2.4.1

, . , . , «»: .





– .





APK. , , .





:





  1. ui



    — , , etc;





  2. — ( ), ( ), etc.





, , , — . MainViewModel



MainActivity



.





, , – .





«» . – . .





buildConfigField



– , null



, .





2.4.2 -

. , . , , .





UseCase



Config



.





GetCardUseCase.kt:





class GetCardUseCase(
    private val netRep: CardNetRepository,
    private val storageRep: CardStorageRepository,
    private val config: CardConfig
) {

    operator fun invoke(): Card? {
        return if (config.isCacheCard()) {
            try {
                val card = netRep.getCard()
                storageRep.save(card)
                card
            } catch (exception: Exception) {
                return storageRep.get()
            }
        } else {
            netRep.getCard()
        }
    }
}

      
      



ui



UseCase



ViewModel



Presenter



.





, : . , .





: NoCardViewModel.kt:






class NoCardViewModel(
    private val getObtainMethodsUseCase: GetObtainMethodsUseCase,
    ...
){
    private val cardObtainMethods by lazy { getObtainMethodsUseCase() }

    val isShowGetVirtualButton by lazy {
        cardObtainMethods.contains(ObtainCardMethod.GENERATE_VIRTUAL)
    }
    val isShowBindPlasticButton by lazy {
        cardObtainMethods.contains(ObtainCardMethod.BIND_PHYSICAL)
    }

    ...
}

      
      



fragment_nocard.xml:





...

<com.google.android.material.button.MaterialButton
    android:id="@+id/no_card_bind_plastic_button"
    ...
    app:isVisible="@{viewmodel.isShowBindPlasticButton}" />

<com.google.android.material.button.MaterialButton
    android:id="@+id/no_card_get_virtual_button"
    ...
    app:isVisible="@{viewmodel.isShowGetVirtualButton}" />

...

      
      



2.4.3

.





, – , , . , :





, . , — , . — CardInfoFragment.





3

White Label android-, , :





✅ – , 10 100;





✅ – , , ( , ).





, best practices . , White Label android- .





, — , . .





— , ! «» , :)





4 ?

  • , ? White Label, . , – , .





  • , CI CD. Azure Devops.





  • , flavors – flavors json .





  • , ? .





, , – !





PS Shout-out à Dmitry Alekseenkov pour une énorme contribution au développement de l'application Android, Valeria Vasilyeva pour l'édition sensible, Valeria Panakova pour des illustrations vives, et le studio Live Typing et l'équipe Loyaki en général pour avoir rendu cet article possible :)








All Articles