Git Product home page Git Product logo

gandalf's Introduction

Gandalf Build Status Download Android Arsenal

In the lifetime of any application there will come a time where you need to drop support for a feature, end of life a product, notify about maintenance, any number of other reasons, Gandalf is here to help!

Gandalf will easily add a check to a remote file that can notify a user with a simple alert, inform them of an optional update, and in dire situations block the user from accessing older versions of the application completely (ex: security vulnerability has been found).

Need an iOS version?

You're in luck! Gandalf was built in parallel with its iOS counterpart, LaunchGate.

Download

Gandalf is hosted on the jCenter repository and can be downloaded via Gradle:

compile 'com.btkelly:gandalf:{latest_version}'

Usage

The goal of Gandalf was to add this basic boiler plate code to any application quickly. You will need to add the following code to your application as well as host a JSON file on a publicly accessible server.

Application Class

Extend the Android Application class and add the following to the onCreate()

public class CustomApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        new Gandalf.Installer()
                .setContext(this)
                .setPackageName("com.my.package")
                .setBootstrapUrl("http://www.example.com/bootstrap.json")
                .install();
    }
}

Splash Activity

Extend GandalfActivity for use as your main "Splash" type activity, this is where the magic will happen. Just provide a layout resource id to display while the bootstrap file is being checked and implement the youShallPass() method with what should happen after a successful check.

public class SplashActivity extends GandalfActivity {

    @Override
    public void youShallPass() {
        //After a successful bootstrap check we change the content view, you may also load a new activity or do whatever logic you want after the check is complete.
        setContentView(R.layout.activity_splash_finished_loading);
    }

    @Override
    public int contentView() {
        //While the bootstrap check is running we provide a layout to be displayed
        return R.layout.activity_splash_loading;
    }
}

Manifest Changes

Add the android:name attribute to the application tag and specify the path to your custom Application class from above and set your SplashActivity as the entry point for your app.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.github.btkelly.gandalf.example">

    <application
        android:name=".CustomApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <activity
            android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

JSON File

You must host a JSON file remotely and set the URL of this file in the the Gandalf installer. The JSON file use the Android versionCode not the versionName for version information. By default the format must match the file included below, if you would like to use custom JSON you can provide a custom deserializer.

{
  "android": {
    "alert": {
      "message": "We are currently performing server maintenance. Please try again later.",
      "blocking": true
    },
    "optionalUpdate": {
      "optionalVersion": "6",
      "message": "A new version of the application is available, please click below to update to the latest version."
    },
    "requiredUpdate": {
      "minimumVersion": "7",
      "message": "A new version of the application is available and is required to continue, please click below to update to the latest version."
    }
  }
}

That's all that's needed to get Gandalf up and running using the basic settings.

If extending GandalfActivity doesn't work for you the Gandalf class can be used directly by calling shallIPass(GandalfCallback callback). In this case make sure you respond to the callback methods and make a call to gandalf.save(Alert alert) and gandalf.save(OptionalUpdate optionalUpdate) if not using the BootstrapDialogUtil for your UI.

Custom titles, buttons and messages

By default, Gandalf provides default title and button text, and gets the message to display to the user from the JSON file.

However, you are able to use your own strings. To do so, you should use the DialogStringHolder class when installing Gandalf.

  1. If you do not provide a DialogStringHolder during installation, a default instance will be used.
  2. If you do not provide message strings in the DialogStringHolder, the message from the JSON file will be used.
  3. If you provide DialogStringHolder but do not set some field manually, default values will be used for all unset strings.
  4. You could either pass a String instance or a string resource id.

Remember: you are not forced to set every string : default values will be used for unset string.

DialogStringsHolder dialogStringsHolder = new DialogStringsHolder(this);

// Defines custom dialog titles
dialogStringsHolder.setAlertTitle(R.string.alert_title);
dialogStringsHolder.setUpdateAvailableTitle(R.string.update_available_title);
dialogStringsHolder.setUpdateRequiredTitle(R.string.update_required_title);

// Defines custom button text
dialogStringsHolder.setCloseAppButtonText(R.string.close_app_button);
dialogStringsHolder.setDownloadUpdateButtonText(R.string.download_update_button);
dialogStringsHolder.setOkButtonText(R.string.ok_button);
dialogStringsHolder.setSkipUpdateButtonText(R.string.skip_update_button);

// Defines custom messages
dialogStringsHolder.setUpdateAvailableMessage(R.string.optional_update_message);
dialogStringsHolder.setUpdateRequiredMessage(R.string.required_update_message);
dialogStringsHolder.setAlertMessage(R.string.required_update_message);

new Gandalf.Installer()
        .setContext(this)
        .setPackageName("com.my.package")
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .setDialogStringsHolder(dialogStringsHolder) // Set the custom DialogStringsHolder
        .install();

Custom OnUpdateSelectedListener

You may provide a custom listener to be invoked when the user selects to update their app. This can be helpful if you are not hosting your application on Google Play and would like to download an APK from another source. Two default listeners are already provided, the PlayStoreUpdateListener which opens Google Play to the specified package name and the FileDownloadUpdateListener which will download a file specified by the Uri provided.

new Gandalf.Installer()
        .setContext(this)
        .setOnUpdateSelectedListener(new OnUpdateSelectedListener() {
            @Override
            public void onUpdateSelected(@NonNull Activity activity) {
                //Perform some action when the user would like to update
            }
        })
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .install();

Custom JSON Deserializer

You may have a different JSON format for the bootstrap file, no problem! To do this you must provide a JsonDeserializer<Bootstrap> during the Gandalf installation.

new Gandalf.Installer()
        .setContext(this)
        .setPackageName("com.my.package")
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .setCustomDeserializer(new JsonDeserializer<Bootstrap>() {
             @Override
             public Bootstrap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                //Inspect the JsonElement object to retrieve the pieces of the Bootstrap file and return using the builder like below
                 return new Bootstrap.Builder()
                         .setAlertBlocking(false)
                         .setAlertMessage("Down for maintenance.")
                         .setOptionalVersion("8")
                         .setOptionalMessage("There is a newer version of the app, please update below.")
                         .setMinimumVersion("6")
                         .setRequiredMessage("You must update to the latest version of the app.")
                         .build();
             }
         })
        .install();

Example App

Included in the source is a simple example application showing four different launch states based on a remote file. You can load and relaunch the app with each scenario by selecting an option in the Android menu.

Screenshots

License

Copyright 2020 Bryan Kelly

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.

You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

gandalf's People

Contributors

albinpoignot avatar btkelly avatar foobar2016 avatar friederbluemle avatar jsibbold avatar pierreduchemin avatar stkent avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gandalf's Issues

Asyncronous update checking

The GandalfActivity approach blocks the UI leading to a unwanted delay on startup.

Is there any example of how to use Gandalf e.g. with a background Service/IntentService?

StrictMode policy violation found when doing Gson mapping from onResponse() call

I have added the following strict mode policy checks in my application onCreate() method.

private fun setStrictMode() {
        StrictMode.setThreadPolicy(
            StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()
                .detectDiskWrites()
                .detectAll()   // or .detectAll() for all detectable problems
                .penaltyLog()
                .build()
        )
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        StrictMode.setVmPolicy(
             StrictMode.VmPolicy.Builder()
                 .detectNonSdkApiUsage()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .build()
         )
    }
}

and found NonSdkApiUsedViolation when trying to map the results from the BootstrapApi.onResponse() callback.

2019-11-01 16:18:42.856 19210-19276/com.sample.test.ui D/StrictMode: StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Lsun/misc/Unsafe;->theUnsafe:Lsun/misc/Unsafe;
        at android.os.StrictMode.lambda$static$1(StrictMode.java:428)
        at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
        at java.lang.Class.getDeclaredField(Native Method)
        at com.google.gson.internal.UnsafeAllocator.create(UnsafeAllocator.java:41)
        at com.google.gson.internal.ConstructorConstructor$14.<init>(ConstructorConstructor.java:221)
        at com.google.gson.internal.ConstructorConstructor.newUnsafeAllocator(ConstructorConstructor.java:220)
        at com.google.gson.internal.ConstructorConstructor.get(ConstructorConstructor.java:96)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:101)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.Gson.fromJson(Gson.java:926)
        at com.google.gson.Gson.fromJson(Gson.java:892)
        at com.google.gson.Gson.fromJson(Gson.java:841)
        at com.google.gson.Gson.fromJson(Gson.java:813)
        at io.github.btkelly.gandalf.network.BootstrapApi$1.onResponse(BootstrapApi.java:118)
        at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

Add dialog popup for events resulting from an update

Add a utility that can show dialogs based on forced update vs optional update and a listener interface for calling code to react to user events on the dialogs. This should be dumb and only show the information in a dialog.

Auto install apk when using FileDownloadUpdateListener

Have you consider initiating the apk install after downloading an apk using the FileDownloadUpdateListener? I have seen a few apps do this in the past so I know it can be done. If you wouldn't want to do this in the library, I can understand that, but maybe we could look into giving a callback instead of finishing the activity in onUpdateSelected. Let me know your thoughts.

Technology stack we talked about

We decided on the following tech stack to get us to version 1.0, any objections or things to add to this list?

  • OkHttp for networking
  • GSON for JSON parsing
  • SharedPrefs for storage
  • AppCompat for dialog display
  • Crashlytics if possible on a library project.

Create callback interface for VersionChecker

Create a callback interface to report version check results. Should include methods such as:

void onRequiredUpdate(RequiredUpdate requiredUpdate);
void onOptionalUpdate(OptionalUpdate optionalUpdate);
void onAlert(Alert alert);

This will be a main hook for the Builder in case consumers want to completely customize the responses to these situations.

Add version checking logic

The library should be able to look at various forms of version strings or version codes and deduce which version is higher / lower. This will involve major and minor numbers, ex "1.2.1 vs 1.3.0" and "1.5 vs 2.1" as well as a version code that may be only an integer. The consumer should be able to configure what the library is evaluating, the version name or the version code.

Add a debug mode to the installer

Add a debug mode to the install process to set a global debug flag for Gandalf. This flag should be used to allow Gandalf to be more error tolerant during release builds. When in debug mode Gandalf should loudly throw exceptions when things are unexpected to allow a developer to resolve the issue easily. When in release mode Gandalf should swallow all exceptions if possible and log them based on the LogLevel set during install. This will remove the possibility of an error in configuration causing the app to crash or block the user from using the app completely.

Create a VersionChecker

Create a class to handle checking the Bootstrap object returned from the web call. This will need to assess in a priority order, prioritizing first the Required update, then Alert and Optional. Will report results to a callback interface #23

This class will also handle writing to SharedPreferences, and comparing the response with what is currently in SharedPreferences.

Add ability for consumer defined deserializer

Add the ability to override the default JSON deserializer allowing the case of a special JSON structure. This will require the consumer to convert this to our model but allows any JSON structure to be used.

This should also included an updated README section at the bottom informing the consumer of the ability to provide a custom deserializer and thus custom JSON.

Message display manager

A simple manager that will check / update values based on a bootstrap object (JSON from server) and determine if the app should display a dialog or not. This should also be notified when a user interacts with a dialog and update the corresponding data in shared prefs.

Values to be compared against past values:

  • Optional version with optional message
  • Alerta message

Application label causing manifest merger issues

Gradle is failing when trying to merge manifest files. I try to use the replace attribute as suggested but it does not work. Have you come across this at all? What is the purpose of the label being defined in your library's manifest? Comparing to other popular third-party android libraries, the label attribute is not usually defined in the library manifest.

Attribute application@label value=(ClinicalKey DEBUG) from AndroidManifest.xml:20:9-41
is also present at [com.btkelly:gandalf:1.3.0] AndroidManifest.xml:15:9-41 value=(@string/app_name).
Suggestion: add 'tools:replace="android:label"' to element at AndroidManifest.xml:15:5-19:11 to override.

Proposed update/message JSON structure

{
    "android": {
        "alert": {
            "message": "We are currently performing server maintenance. Please try again later.",
            "blocking": true
        },
        "optionalUpdate": {
            "version": "3.9.0",
            "message": "A new version of the application is available, please click below to update to the latest version."
        },
        "requiredUpdate": {
            "version": "3.8.0",
            "message": "A new version of the application is available and is required to continue, please click below to update to the latest version."
        }
    }
}

Fix Travis build issue with javadoc generation

The build is failing when trying to generate the javadocs on Travis only. This seems to be related to how the aar's are store on Travis' build system.

Seems to be a few postings about this issue (http://stackoverflow.com/questions/26488015/class-file-for-android-support-v4-widget-drawerlayoutimpl-not-found-on-travis-su)

Stack Trace:
:gandalf:generateReleaseJavadoc/home/travis/build/btkelly/gandalf/gandalf/src/main/java/io/github/btkelly/gandalf/activities/GandalfActivity.java:33: error: cannot access DrawerLayoutImpl
public abstract class GandalfActivity extends AppCompatActivity implements GandalfCallback, BootstrapDialogUtil.BootstrapDialogListener {
^
class file for android.support.v4.widget.DrawerLayoutImpl not found
java.lang.NullPointerException

Found DiskReadViolation when strict mode enabled

In my sample app's onCreate() method i am trying to get gandalf instance as follows:

this.gandalf = Gandalf.get()
this.gandalf.shallIPass(this) 

then i found the following disk read violation and it's happening more frequently whenever i am coming back to app either from background or from back-stack.

Please find the stack trace when we enable strict mode policy in our app as follows:

2019-11-01 14:44:54.701 17750-17750/com.sample.test.ui D/StrictMode: StrictMode policy violation; ~duration=119 ms: android.os.strictmode.DiskReadViolation
        at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
        at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:251)
        at java.io.File.exists(File.java:815)
        at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:605)
        at android.app.ContextImpl.ensurePrivateCacheDirExists(ContextImpl.java:601)
        at android.app.ContextImpl.getCacheDir(ContextImpl.java:694)
        at android.content.ContextWrapper.getCacheDir(ContextWrapper.java:269)
        at io.github.btkelly.gandalf.network.BootstrapApi.<init>(BootstrapApi.java:70)
        at io.github.btkelly.gandalf.Gandalf.shallIPass(Gandalf.java:140)
        at com.sample.test.ui.SplashActivity.onCreate(SplashActivity.kt:54)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Add to the example app

After we have a working library we should add to the included example app that will show a complete working example of the library. This should use a local file so it can be ran without a hosted one, maybe a file URI or a mock web service to intercept a network request. We will have to investigate.

Create Gandalf GitHub io page

Using the GitHub page generator create a page for Gandalf and fill it with documentation, licensing info, description, funny things...

Gandalf not working in Samsung 10

Anyone else having issues with a gandalf verifying if the app is coming from the play store? I have a message coming up the app isn't the real app and blocks the users from the next process. This is only occurring in Samsung 10 devices

Create Gandalf builder to configure library in the application class

Create a builder pattern that allows the consumer to configure Gandalf for their specific use case. This will for sure include setting the url to fetch the update file. Not sure what else is required for the first release, custom JSON deserializer for sure at a later time.

Thoughts on other configurable features for the first release?

Add abstract Gandalf activity

Add an activity that a consumer can extend to easily add Gandalf to their app. This should mark all start lifecycle callbacks as final so the consumer cannot override their behavior. An abstract function should be called when the application should continue launching. This would be called when there isn't an applicable update or after the user has opted to skip a dialog allowing the consumer to be completely unaware the check even happened.

We discussed using an abstract getContentViewId or the Google way of messing with the theme like they do on Google Maps, might want to look into both.

Update README for custom dialog content

The newly added DialogStringsHolder provides the ability to customize the strings used for titles and buttons which is awesome. Right now this isn't mentioned in the README, we should add a section above the "Custom JSON Deserializer" showing a quick example.

Major versions and minor versions not supported

Looking at the source, gandalf does an Integer compare of the version numbers. It would be nice if it could do major and minor version number comparisons as well, such that:

v3.2 > v3.1
v3.12 > v3.8
v3.12.3 > v3.12.06

Gradle Error: Cannot create variant 'android-lint' after configuration ':gandalf:debugRuntimeElements' has been resolved

Hello
i have android studio 3.1.3 and dependencies classpath 'com.android.tools.build:gradle:3.1.3' and am trying to run the code but im getting this error:
Cannot create variant 'android-lint' after configuration ':gandalf:debugRuntimeElements' has been resolved.

I've checked with old gradle version (4.1) and it worked.
Seems, the current gradle 4.4 is not supported by your build scripts

any idea, how to solve it?

gandalf error

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.