Taking inspiration from The Twelve-Factor App: V. Build, release, run, I'm working on updating our CI/CD pipeline with these three distinct steps in mind for an app being built with react-native-web.
Specifically, I want to:
Build: generate an environment agnostic artifact of the code for each platform (web, android, ios)
Release: take an artifact and a config file (API URLs, API keys, debug settings, etc), and release to each platform
This is trivial for web, which is what The Twelve-Factor App had in mind. My question is how do I read a config file on mobile platforms and how can I incorporate this with react-native-web build artifacts? Does my artifact need to contain all of the source code and dependencies so I can pull in the config at release time and build then?
Ideally, each artifact would contain code compiled for each platform that somehow knows how to pull in a config file and do something with it. Next best would be to have the source code for each platform that I can compile with a config file at release time. Third best is have a way to give each distribution enough information at release time so it can request the config at runtime.
Full disclosure, I know nothing about building and deploying mobile apps so I apologize if there is an obvious solution for this!
It's similar for Android. Once the build binary is created it's immutable.
So unfortunately that negates option #1. We can't do anything else with the binary once we build it.
I think for react-native option two is the best approach.
Essentially you'll need to build the apps at release time once you have resolved what your configs need to be. That avoids any overhead of loading stuff at runtime in option #3 and still matches nicely to Twelve Factor. You'll still have a mobile binary that matches the same configuration as your release type.
For actually reading those values, you can just drop the config file into the project's root and we can help with the setup to pull them in.
I'll be glad to discuss those details if you'd like.
UPDATE:
Anything iOS does we can do (almost as well)
Current build tools compile all code into bytecode classes.dex and compress all resrouces into resrouces.arc but res/raw is left untouched.
This gives us a place to inject our files.
From there, the app will be able to read and parse at runtime.
For iOS, the build and (non-App Store) release process works like this at a high level:
Archive your project in Xcode, which results in an .xcarchive artifact.
Export your archive which signs and generates an .ipa file.
Either host this .ipa file yourself (with some additional metadata files) or upload it to a service like HockeyApp for distribution.
There are a few ways that you can manage config inside an Xcode project. One fairly common and straightforward way is to use the info.plist file to store custom keys and values. The app can then look up and use these values at runtime.
In the scenario you describe, it sounds like you want to be able to inject specific config values after step 2 but before step 3. Fortunately, the .ipa file generated during step 2 can be extracted, which will reveal a Payload folder containing an .app file. This file can be inspected, and inside you will see, amongst other things, the app's Info.plist. Modifying this file will allow for injection of whatever config values you want to set.
This will save needing to manage configs inside the Xcode project, and creating a separate archive for each configuration of the app. However what this doesn't solve is step 3. You are still going to need to distribute each configured .ipa file separately.
Building iOS and Android apps with only one project is pretty awesome. But is it also possible to build multiple apps with different names, icons, etc.
This could be very helpful if you build apps with the same layout and maybe 95% same of the functions/code/algorithms:
different target-groups (recipes app for fitness, vegans, veggies, eco,...)
different sports (news for football, basketball, tennis ...)
different customers (business app for customer A, B and C)
...
Things, which could be different per app:
App name
App icons
SplashScreen
Design
Settings (API URL, ...)
Some code (default function/components maybe could get overwritten by app-based custom files)
Maybe it would be the perfect solution if you have a directory flavors where you can put all the files which you want to use to overwrite the default code-base.
Somebody here who released something similar or any ideas how to solve this?
This is a problem I have been solving in my projects last days. And I think I have some good (not perfect) solution so far.
Idea. We don`t have any built-in functions to help us handle this case, so we can use some external manipulation that can do the job. What we need is just multiple Android and iOS projects and single JS code base.
Implementation. I`ve come to this project structure:
ios
android
manager
src
ios and android folders is well known to you, they are native project folders.
src is a folder with JS code, you can organize it whatever you want
manager is most interesting one. Here we can store multiple native project files. We can have multiple native project folders in it, for example app1-android app1-ios app2-android app2-ios. And when we want to work on app1 we just copy app1-ios and app1-android to our root ios and android folders, so the actual project is app1. When we are done with app1 we can save android and ios folders to our manager and then just copy app2-ios and app2-android to root ios and android. And we are all good to develop our app2.
We can do it manually. But we are developers and we can make it much easier. I`ve written a PHP script that makes it as easy as php save.php -a app1 and php set.php -a app1 to copy from manager to root and backwards. Moreover, it takes care of not copying some unimportant staff (pods folder in ios, build in android etc.) and running pod install to ensure we have all pod in actual project.
I stick to one package.json file to have all npm modules installed once, so I don`t have to run npm install after each project switch.
Afterall, we can have as much projects as we wish in one repo, we can customize each one independently.
PS If you want me to share my scripts I`ll do it as fast as I can (need some time to prepare it for github and to write some more detailed instructions), just let me know.
I did this for about 100 different native apps per platform (iOS/Android), sharing the same code base. I manage this setup for about 2 years now and I am still happy with it.
Here is the iOS part:
I use targets with different plist/pch files.
Save files that shuld differ per app in a separate directories, named by the app name. Check the corresponding target, if adding the file to the project:
By using the same filename you can "override" the file for different targets.
The Android part:
I use one gradle module for the common part and one module per app for the customizing. I also use gradle variables for common values definitions (minSdkVersion, compileSdkVersion etc.). Only the following settings differs per app: applicationId, versionCode, versionName. Here is an example file for a custom app:
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "..."
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 28
versionName "1.0"
}
signingConfigs rootProject.ext.signingConfigs
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
dependencies {
compile project(':apps:common')
}
You can override the resources (app icon, images, strings etc.) in the corresponding res folder of an app module (use the same filename for the same resource).
Don't forget to include all your modules in the settings.gradle.
You can use multiple targets.
You can create target by below steps :
Go to project navigator -> Right Click on your app name under target -> Select Duplicate -> from popup select Duplicate only .
And you will find duplicate target. Now you can take another or extra app icon and launch image in your assets and for that target you can set it.
Refer below screenshots for better understanding!
So, select newly maded target and make changes by selecting it from general tab.
You can refer this great tutorial by Appcoda!
We do something similar to what you want do, but on a much more limited range. We have various builds of our app, each with a name, logo, design, API URL...
It's only built for android, so it's essentially a few flavors as mentioned in the comments above. (I don't know what the equivalent system is for iOS)
But we also use https://github.com/luggit/react-native-config that provides a way to share these specific variables/settings between both the native (android and iOS) and javascript code. All you need is setup a .env.myversion file which contains the settings you need, something like:
API_URL=https://my.server.com/
LOGO_FILE=/path/to/the/right/logo
COLORSET=shades_of_blue
The correct file is pulled in at build-time so your app has the correct version of each setting and you can then call these variables from within your code, and branch based on what version you're using. The benefit is that it works everywhere. The downside to this is that you might be shipping some extra stuff with your apps if you have all this in the JS layer rather than defining it in the build system with flavors. I guess it's a tradeoff between convenience and performance/efficiency that you need to decide on based on your use-case.
In Xcode you can simply duplicate your Target and create a new one with a new plist which means you can change the app name, icon, splash bundle and other settings as required.
In XCode you can Make different build confiugrations for different-different apps in single project(means Dev,alpha,beta,Prod,Debug,Release),With the help of this solution you are able to change AppName,AppIcon,BundleId,URLs,SplashScreen etc for different builds.
I created a website, where users can specify their content, icons, name and package name, and many other options like including advertisement SDK in app. After small payment, user can get created APK with certificate or sign app manually. Generation of apk takes a few seconds. APK is ready to upload to PlayStore. This is project was with high price, but my customer is happy.
How it done?
Create some templates for code. You can buy it on some sites.
Split optional code in separate files
If you can't splite code in separate file (for AndroidManifest.xml, for example), you can include comment with some keys (instructions for post-processing)
Generate icons from user content/specified icons.
On user request, just copy all selected files in same directory, with simple text processor remove unnecessary code (use comments from step 3), complile it, ship APK to user.
Basically there are two different options:
Option 1: OS Builds/android flavours:
Using iOS Builds and/or android flavours have some disadvantages in a react-native project:
React-native link only links the first target on Xcode. You’ll have to manually link libraries for further targets. However in react-native 0.60+ there is auto-linking, and this could be fixed with a little tweak to the podfile
React-native run-android may fail to launch the app if the app id differs from the package id.
Might be complex overtime to maintain a single native project with all the different builds/flavours and all the assets and resources that are included in a single project.
Requires some extra knowledge with xcode and android studio/gradel.
Option 2: Natives approach (which I personally prefer)
Based on the answer of Roman Osypov, I have came up with this approach with an implementation.
Currently each react-native project has two native folders, ios and android, each one represents the native project of the platform.
Lets assume we have two different bundles/apps with the same code base but different resources/assets etc..
CabApp TaxiApp
The main idea is to swap but keep in sync the root ios and android folders with their "natives" source.
create a folder in the root of the project called natives
copy the current ios and android folder of the main project to the
natives folder, lets assume it's CabApp, and rename those folder
into the following: CabApp-ios CabApp-android
do the same for TaxiApp, but remember to change the bundleId, the
identifiers, assets etc.. of the native projects
delete ios and android main folders in the root of the project
and add them the .gitignore
install the following dev dependencies yarn add -D rimraf sync-directory ttab
now the first time the project is pulled you need to set your "app"
for example if you want to work on TaxiApp you can execute the
following: yarn ios-TaxApp which you can define in the scripts in
the package.json
yarn rimraf ios && yarn rimraf android && yarn syncdir natives/TaxiApp-ios ios -c && yarn syncdir natives/TaxiApp-android android -c && cd ios && pod install && yarn
Basically this command deletes the root folders ios + android and sync the folders from the natives folders to the root ones.
The final step would be syncing back and watching the modifications from the "temp" ios and android folders back to the natives respective app-platform.
now in your start script for ios for example, you can add the following script
yarn ios-TaxiApp which you can define as:
yarn ttab 'yarn syncdir ios natives/TaxiApp-ios -w -c' & yarn ttab 'yarn syncdir android natives/TaxiApp-android -w -c' & yarn ttab 'yarn react-native run-ios --simulator=\"iPhone 11\"'
I am preparing a white label application (called product later in this post) and I want to set up a very good architecture. This will easily and quickly set up a new client by changing the design, activating features...
I have several servers (dev, pre-prod, prod) so the flavorDimensions server are present in the product and the flavorDimension client is on the client side.
I thought of this solution:
Set up one git per customer and one git for the product.
Clients will have access to the product code by git submodule. This allows me to separate the specific code from my client and the source code of the product.
Git -> Client 1
Submodule -> Product v1
Git -> Client 2
Submodule -> Product v1.2
...
Git -> Product v1.4
But I have a problem how to run it all correctly. The use of flavorDimensions client is hard because I need to make a copy-paste (with gradle) from my submodule to the app module before the build.
The generation of this structure breaks Gradle because its need to be synced every time. (Always Sync Now flag on Android Studio)
So I ask myself, is a good architecture or not ? What do you think ?
Or do you have other ideas and how to implement them?
Thanks for your support.
Hmm interesting take on it.
I have done similar things, but not quite like that. I use flavors for the deltas only. For example, themes, skins, app_icons, and Strings.xml. Maybe a few variations in layout or activities, but do my best to keep it generically served to minimize the maintenance of the code for various customer deployments as each additional flavor slows down the CI process significantly.
However, one key difference in what you are doing is you are not building a platform (aka everyone uses the same code GIT repo and API Server per se, you are building stand alone applications to supply full code and everything else to individual customers.
This sounds like it could be a maintenance nightmare, but if your situation requires that customer's get access to the code base for their product flavor, then I guess it makes sense. Not sure why they would get access to the code though as it is your product you are reskinning for customers right?
At any rate, you aren't asking for me to ask your reasoning haha, you are asking about architectural solutions. So here is my thoughts.
Make a task that copies the respective folders into a new module structure exactly like the modules you have now, but with changes to the package name so that they all slightly differ, then run your GIT push. You can run bash files from Gradle, so you can either write scripts and execute them from relative paths or you can write it directly in your Gradle file itself.
So I would update my Gradle to have separate dependency file like libs.gradle
->Put dependencies in that file like ext.libs {gson:"com.whatever:version"}
->apply from ../libs.gradle
->dependencies { libs.gson, etc..}
So i would assume your build task would do
->Create a new Module from All Source by copying files properly with new names if necessary to rename
->if using maven server, then deploy aar or jar for compiled new module
->Create a new dynamic Gradle file libs.gradle to replace current libs.gradle file that is updated source pointer to deployed artifacts
or created module
->run gradlesync, when complete run assembleRelease, then you can do your git push etc..
All of this can be synchronized and done from terminal and localized into commands. Flavors may not help a ton in this area as they are part of the build process so you kinda need pre build processes which you can create tasks to do pre-build, but the key is the packaging of dependencies and updating your libs.gradle file and making sure that you replace it per flavor. Or if you have fixed flavors you can just make a specific libs.gradle for each flavor and update it based on the compiled dependencies.
I don't know your overall picture, but it is all doable. Hope that helps. Goodluck.
Is there a way in Jenkins to not only read but also write global parameters from a job?
To achieve reading I used this plugin: Global Variable String Parameter
However, I have found nothing yet to support write access to global parameters. I see that this could be seen critical as it can create race conditions.
What I actually want to do:
I have two Jenkins Jobs - each publishing my Android APK to the Google Play Store but into different release tracks (e.g. "release" and "beta").
I want the build number to be incremented automatically via gradle. But gradle needs to access the build number of the last build from anywhere outside of my VCS (to avoid requiring another commit). I want to guarantee that the build numbers are chronologically in order. Hence, keeping a separate build number for every Jenkins Job (e.g. in a "version.properties" file) is not a solution.
A plan B could be to switch to a date encoded build number. But I'm curious if there is a way for the incremental approach.
you can use "version.properties" file located in the jenkins workspace and not the job workspace.
1- inject environment variable from the file
2- build the apk
3- increment the version from the shell script
version=$(($version+1))
echo version=$version > ${JENKINS_HOME}/workspace/module.properties
My server needs to keep building a large amount of Android projects. All of them are almost identical except for minor change on manifest.xml or any resource file (if it's better for the task) for each build. To reduce cost and improve efficiency, I try to implement incremental build. My planned procedures are:
after the first successful build, skip all the previous
procedures (aapt to generate R.java, adle to make java, etc.)
directly call aapt to make resource files, e.g., *.ap_
call apkbuilder to make classes.dex and usigned.apk
make signed.apk
So my question is whether the above solution is possible? And any clue about how to implement it?
This isn't necessarily a solution for your particular requirements but perhaps it will provide you with some useful pointers.
I have an Antlib that I use for building Android projects. You probably won't want to use it yourself as it has some drawbacks, but it should serve as an example of how to perform the various steps to build an Android app using Ant. In particular, it shows how to call the various Android SDK tools from Ant and how to use the Ant uptodate task and Ant's if and unless attributes to avoid processing files that haven't changed.
The source for the Android Ant macros is here (the Antlib documentation might help you to make sense of what it's doing).