Dynamic screens in Android application - android

i am programming an android application which can be use by different business and/or users, each can have its own configuration.
each business will set his required configuration (screens, text, and so) in a back office site (data will be saved in database).
on Splash screen i will fetch a Json string from the server which
contain this business specific configuration.
from now on the application should work base on this json.
my questions:
is it recommended to build it this way?
is it good practice to have a workflow manager class that will take care of screens transitions.
can you please recommend a strategy you would go with.
here is a function that handle screen transitions
public static void startNext(String FromActivity, Context context)
{
try
{
Intent intent = new Intent(context, Class.forName(context.getPackageName() + getNextActivity(FromActivity)));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
catch (ClassNotFoundException ex)
{
Log.e("test", ex.getMessage());
}
}
the getNextActivity function return a class that should be open next based on the json string.
and example of call from activity 1 to move to next activity
FlowManager.startNext(this.getLocalClassName(),getApplicationContext());

When you have a customization like that it is best to make a build script which will download the latest code for a specific publisher from github/svn or something like that. It should also download publisher specific strings, launcher icons, name of the app and the end package package name, version, certificate to sign the application with, assets, rename all occurrences of default package... Make sure you exclude any unnecessary libraries that some publishers don't need and comment out the stuff related to that library. You can define start/end tags in code and then search for the start tag, replace it with /* and then replace the end tag with */ effectively commenting everything between them out. At the end the script runs gradle wrapper. First run clean and then build. It's also good to define the end output location of apk so it's easier to find.
The most easy way to build a build script like that is using bash scripts. They run on linux/mac or even both if you do it the right way. The whole think should run on a build server like Jenkins
After the build is done, it is good to upload the final *.apk to a server where publishers can download the latest apk and then upload it to Google Play Store.
Happy Coding!

Related

avoid login when redeploying on Android Emulator

Trying to speed up my manual testing on Android Emulator by not having to login and navigate to a specific page every time I deploy a new apk to the emulator. Need to make sure that a careless developer on the team cannot accidentally deploy a release to play store that allows skipping login (e.g., don't just uncomment code to skip login).
Current approach:
Create product flavors "normal" and "skiplogin" (no attributes).
Where code grabs REST API credentials from SharedPreferences add this:
if (BuildConfig.DEBUG && BuildConfig.FLAVOR.equals("skiplogin")){
// use hard-coded username/token
}
pros - it works. greatly reduces time spent click testing
cons - "gradle clean build" takes twice as long when you have two flavors. Does not work on pages that depend on state that is fetched on previous activities/fragments, such as data fetched only at login.
Gotta be a simpler way, who knows it?
If what you want is that the code don't run on release why don't you instead use BuildConfig.DEBUG?
if (BuildConfig.DEBUG && BuildConfig.mockLogin) {
// use hard-coded username/token
}
and in your gradle define
buildConfigField('boolean' , 'mockLogin', 'true')
in the case that the code makes it to production it won't run cause BuildConfig.DEBUG it's false when you do a release build, and you can't upload a debug build to play store cause it won't allow it.

Save app data on Android/iOS using Haxe/OpenFL

So, I've created a Haxe function (using the OpenFL library) that saves text and image data to the game's folder on desktop targets. A little while ago, however, I was notified in this thread that the function would not work on mobile targets. The same user, thiagojabur, also gave me a solution (scroll down in the same thread). My main problem is that I don't have a mobile device myself that I can test on (I have a Galaxy Tab4, but I haven't gotten it working with my Haxe projects). Rather than send 100 versions to someone who can test it, I've decided to ask my questions here.
The goal is to save the data into a folder with the game's App ID. For instance, if the App ID is com.potato.potatogame, then the data will be saved (on Android) in the application storage directory, in the folder /Android/data/com.potato.potatogame/. I believe I can get the first part of the path using openfl.utils.SystemPath.applicationStorageDirectory(), but I'm stuck on how to get the App ID. Does anyone know of a function which can do so?
Alternatively, I may be misunderstanding a part of the code. From the way thiagojabur used the code, it would seem that SystemPath.userDirectory() returns "/Android/data/com.potato.potatogame" on Android. Is this correct, or would I still need to build the path manually?
Any help is appreciated. Thanks in advance!
In one of my apps using OpenFL in legacy mode I saved an images using following code:
static var IMAGES_PATH:String = SystemPath.documentsDirectory + "/MyAppName/";
public function saveImage()
{
var filename:String = IMAGES_PATH + name + ".png";
try {
if(!FileSystem.exists(IMAGES_PATH)) {
FileSystem.createDirectory(IMAGES_PATH);
}
File.saveBytes(filename, app.canvas.bitmap.encode("png"));
}
catch(e:Dynamic) {
trace(e);
}
}
With modern non-legacy code, I guess the path getting should be changed to lime.system.System.applicationStorageDirectory
Also if you still need to get the App ID, it can be achieved by Application.current.config.packageName

Cordova / Phonegap: Live update codebase

We are using Cordova along with AngularJS for iOS and Android applications.
One big disadvantage of iOS are the long review times from Apple. In Google's Playstore, your app is available nearly immediately, or within a few hours. But Apple takes ages to review your app, even when it's only a small change.
So I was thinking, if there is a way to support some kind of live update.
That means, I could provide a ZIP file or something else with a new codebase, my app checks for updates and then installs the new files.
I've read something from appmobi, but are there any open source solutions?
cordova-app-loader is an easy to use plugin to update app files via 3 simple steps:
check() for a new manifest
download() files
update() your app!
It supports android and iOS
I don't know of any ready made solutions for that, but it should be easy enough to program something like this on your own.
Here are some points to get you started and to consider:
If you want to distribute updates via zip, you need a nativ plugin which handles the extraction
You might not be able to override files in the default location of your app (depending on OS). So, all files you want to update in the future have to sit in a folder your app has read/write access to (iOS: e.g. Library or Documents folder)
Now you simply need to download the zip-package, unpack the zip to your chosen directory, and restart/reload your app.
you will not be able to update native plugins!
Apple probably doesn't like that, since you are able to change the whole application without passing
their review process
I'm doing this inside my cordova app and haven't had any issues with ios app store review.
I'm using Jquery's ajax function to download both a javascript and a css file from a server that I can change without an app store approval and then I can inject those scripts once they downloaded on app startup.
I tried using the cordova File api and I'd then save the file locally, but offline support ins't the important to me at the moment and Jquery's ajax is much simpler.
Here is the jquery code I use. I have a bundle id that I use to detect if a new javascript file is available, otherwise jquery's ajax caches the previous requests to speed up download time.
This solution lets you have a subset of your code be dynamic. I still have a base set of code that is bundled with the app, along with native plugin js and native code which would need to go through the app store. But this atleast lets me push bug fixes without going through the app store.
Otherwise, I'd look at a solution like this: http://docs.build.phonegap.com/en_US/tools_hydration.md.html
function insertScript(version) {
var scriptUrl = "";
try {
// get javascript file...
scriptUrl = mobileWebServiceUrl + "/DynamicContent/Bundles/Scripts/dynamic";
scriptUrl += "_" + bundleVersion.replace(/\./g, "_") + ".js?v=" + version;
console.log("downloading script: " + scriptUrl);
// Allow user to set any option except for dataType, cache, and url
options = {
dataType: "script",
cache: true,
url: scriptUrl
};
// Use $.ajax() since it is more flexible than $.getScript
// Return the jqXHR object so we can chain callbacks
return $.ajax(options).success(function(response) {
console.log("insertScript success");
dynamicContentScriptLoaded = true;
});
} catch (e) {
//console.error(e);
ReportError("problem downloading javscript: " + scriptUrl);
}
}
function insertCSS(version) {
try {
// get css file...
var cssUrl = mobileWebServiceUrl + "/DynamicContent/Bundles/Css/dynamic";
cssUrl += "_" + bundleVersion.replace(/\./g, "_") + ".css?v=" + version;
console.log("downloading dynamic css: " + cssUrl);
$.ajax(cssUrl)
.success(function (response) {
console.log("successfully downloaded dynamic css");
var script = document.createElement("style");
script.type = "text/css";
script.innerHTML = response;
$('head link').each(function () {
if ($(this).attr('href').search('MobileFrame') > -1) {
$("#MobileFrameCSS").before(script);
}
});
dynamicContentCssLoaded = true;
// TODO: implement caching at a later date
//if (isPhoneGap())
// saveFile("DynamicStyles", response);
});
} catch (e) {
ReportError("problem downloading css");
}
}
Well, Adobe offers exactly that service in their Phonegap Build service. It's called Hydration.
The example shows using it with Android and iOS platforms, so I guess they made it compatible with the iOS Dev Program License Agreement.
If you are using Cordova, you probably will have to switch to the Phonegap CLI if you want to use their build cloud services, which is basically the same as Cordova's with some extra commands to upload to their cloud, etc.
I think there are some plugin like Splashscreen wich also have some minor changes (using <gap>for params into config.xml instead of <preference>). Again, if Hydration solves the problem for you, the changes are minor and you get a really nice feature.
I think the best choice would be to not try to do this with Phonegap, but rather identify your dynamic parts and implement these in Javascript.
Yes, I mean you should indeed use Javascript yourself without Phonegap, for example via JavaScriptBridge:
https://github.com/kishikawakatsumi/JavaScriptBridge
It may require more work initially to redesign your app into a "static" part (your PhoneGap app) and dynamic part (dynamic created views via JavascriptBirdge), and interacte seemlessly between them. But in my opinion, that will be ultimately the best software design.
However, also make sure you still meet Apples AppStore requirements.
The Meteor framework provides exactly this functionality when combined with PhoneGap. It's even sanctioned by Apple in the latest Developer Agreement. Here are some technical details and then some about Apple's view on it.
I think there is no such solution is available, but you can do it by programmatic way.you can update your cardova app by fetching files from server and updating it.
Check out CodePush from Microsoft. Works with Cordova and React Native.
Appears to be very similar to the "live update" feature from Ionic Cloud.
If you migrate to capacitor, the successor of Cordova there open source solution now.
Capacitor-updater, is the only alternative to ionic AppFlow.
The updater allows you to manage update by yourself, store your zip update where you want and use the download method.
How to start
npm install #capgo/capacitor-updater
npx cap sync
Then in your main JS, this is required to let the updater know the update is valid
import { CapacitorUpdater } from '#capgo/capacitor-updater'
CapacitorUpdater.notifyAppReady()
And lately after checking yourself the current version need update:
const version = await CapacitorUpdater.download({
url: 'https://github.com/Cap-go/demo-app/releases/download/0.0.4/dist.zip',
})
await CapacitorUpdater.set(version); // sets the new version, and reloads the app
After many request of people didn't want to do that themselves, I started Capgo a business to manage all the update process.
All is open source and can be replicate on your own as well.
Doing things for Capacitor is now my main activity, I produce open-source plugin as my main channel of Marketing, I'm solo founder and bootstrapped.
Hope my tool will help you !

Releasing packages on Play store

So, I have a fairly large app that I'd like to distribute via packages in the Play store. I see this tactic used quite often. I haven't seen much in the way of actually doing it. From what I've gathered so far the logic in code would work like this:
try {
Start activity in package
} catch (ActivityNotFoundException e) {
Prompt user to play store by using the package name as the URL
}
Aside from that, I'm wondering what kind of setup is required for the packages. They shouldn't be opened from a launcher (my app only).
What intent filters should the packages have applied to them if any?
Is it possible to set up the Manifest in such a way that I can make it unsearchable in Google Play but still be downloaded if linked directly to it?

Auto-Update for (private) Android apps

I'm developing a non-public Android app, i.e. the app won't be available in the global Android Market. The app will be installed on a limited number of clients, e.g. by using an apk file.
How can I enable an auto-update functionality in this app?
I see different potential options (I do not know if those are technically hard or even impossible to implement or if there are any existing functionalities that can be reused):
On each launch the app tests if a new version exists (by requesting a server), if so downloads the new apk and replaces itself with the new version.
Use (or develop?) a separated app or service that undertakes the update-check and replacement-process.
Use (or develop?) a private market app which has an auto-update option. This option is similar to the second one, but more generic: The market app would be connected to a repository, i.e. it would handle an arbitrary number of (private) apps.
I would prefer option one since the auto-update functionality is included in the app which needs less development efforts.
janjonas, in the company I work we had a similar problem with Windows Mobile 6.x, and we use pretty much the same solution pointed by EboMike:
The main app check if it's updated, against a WebService. It receives the current version & the URL from where download the new version, if necessary. The main app then start the Updater app, passing the URL, and quit.
The Updater do the download of the new program, via HTTP, showing to the user the % downloaded. The user can cancel the download anytime, in a controlled way, and the Updater can registry this cancellation.
Since the new app is downloaded, the Updater run the new app, and quit.
I think option one is the least amount of work for you, and actually the cleanest one too since it will go through the proper channel of using Android's built-in package installer which includes user notification and the option for the user to abort the installation if desired.
You already have it all outlined - check for a new version on a server (would be nice to give the user the option to turn that off), and if there is a new version, you could either just link to the URL with the APK (which will, IIRC, use the browser's download manager to download it), or you could download it with your app and then point the intent to your local file. Using the HTTP link is technically less work and cleaner - the more you let the operating system do, the better - unless there's a reason not to.
Enabling "Install non-market app" is still needed for any application outside the Google Play. If it not enabled, the installation process is going to ask for it and redirect the user to the Application Settings, and after that, the user can install the app.
Depending on your needs, you can delegate to a third part lib.
Some of the permissions we'll use to get this done are the following:
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Let me explain a bit... The last, WRITE_EXTERNAL_STORAGE, is self-explanatory. With ACCESS_SUPERUSER we'll tell the system that we intend to use root privileges. READ_EXTERNAL_STORAGE will be needed in the future in order for your app to read files on SD card.
Assuming that you have downloaded the file and that all those devices can be rooted (limited number of clients, not on Play, etc.), you could do this:
String filePath = Environment.getExternalStorageDirectory().toString() + "/your_app_directory/your_app_filename.apk";
Process installProcess = null;
int installResult = -1337;
try {
installProcess = Runtime.getRuntime().exec("su -c pm install -r " + filePath);
} catch (IOException e) {
// Handle IOException the way you like.
}
if (installProcess != null) {
try {
installResult = installProcess.waitFor();
} catch(InterruptedException e) {
// Handle InterruptedException the way you like.
}
if (installResult == 0) {
// Success!
} else {
// Failure. :-/
}
} else {
// Failure 2. :-(
}
Here might be a very lame method but for some companies, if you believe its applicable, this might be very easy to implement.
Create an password screen (passwordActivity) that asks a password to access the application.
Once the password is entered, raise a flag (set a boolean value from false to true using sharedpreferences)
Place the .apk file on Google Store.
Change the password once everyone installs the app, and release a new update on Google Play Store.
Since the software is going to cache the flag value, the password screen won`t show up even the password is change. It will only show up for new installations so might need to repeat the process.
Note: This method might better fit if there is not hundreds of users using the application. And don`t forget this method is also not secure. To sum up, if you are looking a way to keep the application private and have no security concerns, this is what I recommend.
Update app
Make sure that you already have your new apk download on location
void installNewVersion(String location) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(location + "app-debug.apk")),
"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

Categories

Resources