home.

tagged: android

Android: A debug build signed with a constant keystore

Each debug build of your android application will be signed with the debug keystore.
 
This is a problem because in continous integration environments, like travis-ci or docker, the debug keystore is regenerated on each creation of the environment.
 
This means if someone has a debug version of your application, they won't be able to upgrade to a more recent version, because the keystores will be different.
 
To get around this, let's create and add a keystore to our repository for continuous integration environments. Create this in the app/ directory. And ensure the key password is the same as the keystore password.

keytool -genkey -v -keystore ci-key.keystore -alias ci-key-alias -keyalg RSA -keysize 2048 -validity 10000

Now in your app/build.gradle file add this to the android block:

signingConfigs {
  ci {
    keyAlias "ci-key-alias"
    keyPassword System.getEnv("CI_KEYSTORE_PASSWORD")
    storeFile file("ci-key.keystore")
    storePassword System.getEnv("CI_KEYSTORE_PASSWORD")
  }
}

We give it the alias and reference to the file we created. We'll get the keystore key from the environment, one injected into the contiuous integration environment, for example.
 
In the same android block, create a new build variant, initalised as the debug variable, called debug_with_ci_keystore:

buildTypes {
    debug_with_ci_keystore.initWith(buildTypes.debug)
    debug_with_ci_keystore {
        if(System.getenv("CI_KEYSTORE_PASSWORD")) signingConfig signingConfigs.ci
        minifyEnabled false
    }
...
}

We give the variant the ci signing config above if we have the correct environment variable, CI_KEYSTORE_PASSWORD. This means, if we don't, we only create app/build/outputs/apk/app-debug_with_ci_keystore-unsigned.apk, not the signed app-debug_with_ci_keystore.apk.
 
Now on ./gradlew build, if we have CI_KEYSTORE_PASSWORD set in our environment, we will create app/build/outputs/apk/app-debug_with_ci_keystore.apk, which will have a constant keystore.
 
The above obviously works for a release signing: just change the build type to release, although you may not want to keep your release keystore in your repository.

android android-keystore keytool


Android: Continuous Deployment with HockeyApp

Continuous Deployment is automatically distributing new app to your testers, and automatically reporting crashes. This guide shows you how to use HockeyApp for the distribution aspect.
 
I'm going to assume you've got a basic Android App in development.
  1. First, sign up to https://HockeyApp.net
  2. Click add new app, and choose to add it manually
  3. Give it the same package name as appears in your AndroidManifest.xml
  4. Now it's created, click on integrate HockeyApp quick tutorial. It will tell you add these parts to your app:
 
   app/build.gradle:
    
      repositories {
        ...
        jcenter()
        ...
      }
      
      android {
        ...
        defaultConfig {
          ...
          manifestPlaceholders = [HOCKEYAPP_APP_ID: "IT_WILL_TELL_YOU_YOUR_APP_ID"]
          ...
        }
        ...
      dependencies {
        ...
        compile 'net.hockeyapp.android:HockeySDK:4.0.1'
        ....
      }      
 
 app/src/main/AndroidManifest.xml (within the application tag):
 
      <meta-data android:name="net.hockeyapp.android.appIdentifier" android:value="${HOCKEYAPP_APP_ID}" />
 
 In your Activity:
 
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Your own code to create the view
        // ...
    
        checkForUpdates();
      }
    
      @Override
      public void onResume() {
        super.onResume();
        // ... your own onResume implementation
        checkForCrashes();
      }
    
      @Override
      public void onPause() {
        super.onPause();
        unregisterManagers();
      }
    
      @Override
      public void onDestroy() {
        super.onDestroy();
        unregisterManagers();
      }
    
      private void checkForCrashes() {
        CrashManager.register(this);
      }
    
      private void checkForUpdates() {
        // Remove this for store builds!
        UpdateManager.register(this);
      }
    
      private void unregisterManagers() {
        UpdateManager.unregister();
      }  

0. Now build and install that on your phone, and upload the APK to HockeyApp via the upload version button, clicking through all the dialog boxes until you can see the version on the Overview.
0. Now change something in the app, like some text, and update the versionCode in app/build.grade. Build it, but do not install this to your device (so we can see automatic updates on our phone - you don't normally do this)
0. With this newly build version, upload it to HockeyApp as before.
 
Now when you open the app again, or do something to trigger onResume(), it will ask you if you want to update to the latest version.
 
Click update, and voila - you and your testers will see the newest app, and any crashes will be reported to you with a stacktrace and device information.
 
We don't yet automatically upload our APK to HockeyApp via a build server / continuous integration environment, or send up the release notes, but we can do that in a later tutorial.

android hockeyapp


Android and Facebook's Litho: Getting started

Litho allows you to declare your view in code with speed optimisations for lists. It helps with reactive flows.
 
It uses flexbox layout logic, via the Facebook Yoga library, allowing you to use your existing web knowledge. This applies to iOS too since Yoga also exists for iOS.
 
It supports one-directional data binding, thereby allowing you dive into the flux architecture a little.
 
Let's do the basic getting started first. Let's bung all the depenedencies into your app's build.gradle.

compile 'com.facebook.litho:litho-core:0.2.0'
compile 'com.facebook.litho:litho-widget:0.2.0'
provided 'com.facebook.litho:litho-annotations:0.2.0'
annotationProcessor 'com.facebook.litho:litho-processor:0.2.0'
compile 'com.facebook.soloader:soloader:0.2.0'
debugCompile 'com.facebook.litho:litho-stetho:0.2.0'
compile 'com.facebook.litho:litho-fresco:0.2.0'

Create an Application class in your app and ensure your manifest points to it. In the onCreate method add this:

SoLoader.init(this, false);

In your Activity's onCreate change the view layout code to:

 ComponentContext c = new ComponentContext(this);
 setContentView(LithoView.create(c, MyComponent.create(c).build()));

We're creating a Litho context, and then creating a Litho view with that context, and a component too. Where does that MyComponent come from?

@LayoutSpec
public class MyComponentSpec {
    @OnCreateLayout
    static ComponentLayout onCreateLayout(ComponentContext c) {
        return Column.create(c)
                .paddingDip(YogaEdge.ALL, 16)
                .child(Text.create(c)
                        .text("sup")
                        .textSizeSp(40))
                .child(Text.create(c)
                        .text("sup2")
                        .textSizeSp(20))
                .build();

    }
}

The annoation @LayoutSpec takes the class name minus Spec, thereby creating the MyComponent class via facebook's annoation processor.
 
We create a Column using flexbox terminology, with two Text children, which are not TextViews incidentally.
 
Later tutorials will focus on using Android Views within Litho and events I should think.

android litho


Android: Cache network requests for offline access with Retrofit2 and OkHTTP3

Let's first build a OKHTTP client with
  1. a cache
  2. an interceptor that checks for connectivity and, if none, asks for cached data:
Here's the client.

OkHttpClient client = new OkHttpClient
  .Builder()
  .cache(new Cache(App.sApp.getCacheDir(), 10 * 1024 * 1024)) // 10 MB
  .addInterceptor(new Interceptor() {
    @Override public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      if (App.isNetworkAvailable()) {
        request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
      } else {
        request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7).build();
      }
      return chain.proceed(request);
    }
  })
  .build();

We first create the cache object with 10 MB, getting the cache directory from a static Application context.
 
Then the Interceptor uses a utility method in my Application class to check for connectivity. If there is connectivity, we tell the request it can reuse the data for sixty seconds.
 
If there's no connectivity, we ask to be given only (only-if-cached) 'stale' data upto 7 days ago.
 
Now make this OkHTTP client your client for Retrofit2 and you will be able to use your old cached data when the app goes offline.

android android-retrofit android-okhttp


Android: ViewDragHelper tutorial

If you want to drag things around your screen, you'll want to use the compatability library's ViewDragHelper.
 
You'll first need to make your own ViewGroup extending custom view, since we'll be intercepting MotionEvents.

Creating an instance


Once you've made your custom view extending ViewGroup, you need to create a ViewDragHelper instance. We'll put it in onAttachedToWindow().

@Override
protected void onAttachedToWindow() {
	super.onAttachedToWindow();
	mDragHelper = ViewDragHelper.create(this, 1.0f, new OurViewDragHelperCallbacks());
	...
}

We're using the create() factory method, passing in this ViewGroup, the sensitivity for a drag start (1.0f is normally according to the docs), and some callbacks.

Motion events


Before we look at the callbacks, let's look at how they are activiated, and for that we look at onInterceptTouchEvent() and onTouchEvent().

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  boolean shouldInterceptTouchEvent = mDragHelper.shouldInterceptTouchEvent(ev);
	return shouldInterceptTouchEvent;
}

This method uses our drag helper to decide if our class should intercept the touch events or not. We use this so if our child view is a button, for example, we can both press and slide it.
 
Next we use the onTouchEvent() method to make the view drag helper process this motion event, which will be called according to the interaction of the view and the method above.

@Override
public boolean onTouchEvent(MotionEvent event) {
	mDragHelper.processTouchEvent(event);
	return true;
}

ViewDragHelper callbacks

  
Now we have the MotionEvents being passed correctly to our ViewDragHelper instance, we can look at the callbacks.

new ViewDragHelper.Callback() {

	@Override
	public boolean tryCaptureView(View arg0, int pointerId) {
		return true; 
	}
	

The above will be passed the views which are dragged, and allow you to say if they should be captured or not. In this case we're saying deal with any of the ViewGroup's children.
	
	@Override
	public int clampViewPositionVertical(View child, int top, int dy) {
		return top;
	}
	

The above method allows us to drag on the vertical axis. If we're happy with the new position of the drag, we just return the 'top' value. You can define clamp the dragging in certain regions using this method.
 
This has a sister, clampViewPositionHorizonal, that allows you to drag or constrain on the horizonal axis.
	

	@Override
	public int getViewVerticalDragRange(View child) {
		return parent.getMeasuredHeight()-child.getMeasuredHeight();
	}
	

The above method is used to calculate the velocity internally, by knowing your view's dragging space. Again, it has a sister method, replacing Vertical for Horizontal.
	
	@Override
	public void onViewReleased(View releasedChild, float xvel, float yvel) {
		super.onViewReleased(releasedChild, xvel, yvel);
		if(yvel>0) {
			mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), parent.getMeasuredHeight()-releasedChild.getMeasuredHeight());
		} else {
			mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), 0);
		}
		invalidate();
	}
});
	

The above method, and final we'll look at, is called when the dragging of an element stops. We're using it to see if the velocity of the drag in the Y axis is positive or negative.
 
If it's positive, i.e. we're dragging down, we take the released view and tell it slide down down to the bottom of the parent. If it's negative, i.e. we're dragging up, we slide up to the top.
 
We finally call invalidate so the animation can begin. There are other methods we can call on the drag helper, like smoothSlideViewTo, but settleCatpuredViewAt takes into account the current velocity.

Ensuring the animation continues


After we call the settleCapturedViewAt or similar methods above, we need to ensure the animatin continues. In the computeScroll() method on our ViewGroup or similar we have the following:

@Override
public void computeScroll() {
	super.computeScroll();
	if(mDragHelper.continueSettling(true)) {
		ViewCompat.postInvalidateOnAnimation(this);
	}
}
	

We call the continueSettling method on the ViewDragHelper instance so our animation continues, and if it's not yet settled, we then go and call the postInvalidateOnAnimation() method to ensure we keep animating.
 
There are plenty of other methods to play with here https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.html

android android-viewdraghelper

Page 1 of 15
next