mdoery ~ software development

adventures in coding

how to share resources among two or more android apps

| Comments

Summary: This post explains how to use the attribute sharedUserId to make data accessible from one Android app to another. To follow along, you should be familiar with building Android projects in Eclipse. To implement the solution, you will need an Android device; it won’t work in the emulator.

For the current app that I’m building, the user will be given the choice of downloading one or more “packs” which contain new content. Here, “content” means resources such as images or text files in the assets folder of an Android project. Every new pack will be implemented in its own, independent Android project.

As a concrete example, suppose I’ve built a crossword app which allows users to solve crosswords on a mobile device. The app comes with a limited supply of crossword puzzle content. If users want to solve more crosswords, they have to download more packs of puzzles from the Google Play store, or elsewhere. For a real-life example, take a look at Standalone’s Crossword Light app.

The key to making this work lies in the AndroidManifest.xml attribute called sharedUserId.

In keeping with the example above, open Eclipse, and first create the project MainCrossword. This project represents the app which lets users solve crosswords. Note that we won’t implement a real interface; for our purposes, we’ll just provide a simple TextView to display debugging information:

  1. This is a launchable application - meaning it will show up as an App when installed on your device. As you create the new project, accept all the defaults supplied by Eclipse. Pay attention to these options, in particular:
    1. Set the package name to be `com.mdoery.tut.maincrossword`.
    2. Set the Minimum Required SDK property to API 8: Android 2.2 (Froyo), and the Target SDK to API 17: Android 4.2 (Jelly Bean). This tutorial has been tested with that configuration.
    3. When creating the workspace, just choose a blank `Activity` to start with.
  2. Open the `AndroidManifest.xml` in `MainCrossword` and edit the <manifest> tag to add the following property: `android:sharedUserId=”com.mdoery.tut.maincrossword.uniqueid”`.

Next, create another project in Eclipse, called AnimalCrosswordPack. This will be a “data pack” which will contain the content for a number of animal-themed crosswords (note that for our example we will use fake data; we’re not going to build a real app, just enough of it to demonstrate the solution).

  1. Choose the same options as for `MainCrossword`, but this time, name the package `com.mdoery.tut.maincrossword.pack`. There’s nothing special about this package name; I’m just using a different package from the previous one so it’s clear that they don’t have to be the same.
  2. Otherwise, repeat the process as for the `MainCrossword` project (same Target SDK, etc.).
  3. In `AnimalCrosswordPack`, there should be a folder called `assets`. Create a subfolder here called `txt`, and then create the file `animal_theme.txt` within `assets/txt`. Edit this file to contain the text “Lions and tigers and bears, oh my!”.
  4. Open the `AndroidManifest.xml` in `AnimalCrosswordPack` and perform two edits:
    1. delete the <intent-filter> tag. This makes it impossible to launch the app. We don’t want this app to be launched by the user; it’s only there as a source of data to be accessed by the `MainCrossword` app.
    2. edit the <manifest> tag to add the same `sharedUserId`: `android:sharedUserId=”com.mdoery.tut.maincrossword.uniqueid”`.

So the AndroidManifest.xml file in AnimalCrosswordPack should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mdoery.tut.maincrossword.pack"
    android:versionCode="1"
    android:versionName="1.0"
    android:sharedUserId="com.mdoery.tut.maincrossword.uniqueid" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.mdoery.tut.maincrossword.pack.MainActivity">
        </activity>
    </application>
</manifest>

Notice the sharedUserId property. The MainCrossword project also has the android:sharedUserId="com.mdoery.tut.maincrossword.uniqueid" property set. However, it has not had <intent-filter> tag removed.

Eclipse should have created a file activity_main.xml in the directory MainCrossword/res/layout (if it hasn’t, add this now). Edit the file so that it contains a TextView to display the output of our test, as follows:

1
2
3
4
5
6
7
8
9
10
11
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minLines="25"/>
</RelativeLayout>

Now open com.mdoery.tut.maincrossword.MainActivity and edit the code as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.mdoery.tut.maincrossword;

import java.io.InputStream;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.widget.TextView;

/**
 * Demo to show how data can be shared between two apps.
 * 
 * You must install the Android app generated from this project and 
 * from AnimalCrosswordPack project to make this demo work correctly.
 * 
 * @author mdoery
 *
 */
public class MainActivity extends Activity {
    private static String sep = System.getProperty("line.separator");
    // package name of AnimalCrosswordPack
    private static String pkg = "com.mdoery.tut.maincrossword.pack";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // We will write the results of our test to the TextView.
        setContentView(R.layout.activity_main);
        TextView tv = (TextView)this.findViewById(R.id.textView1);
        // Start of displayed content.
        String text = "BEGIN ...." + sep;
        try {
            Context ctx = createPackageContext(pkg,
                    Context.CONTEXT_IGNORE_SECURITY);
            // Add contents of file animal_theme.txt 
            // from AnimalCrosswordPack app.  
            text += getText("txt/animal_theme.txt", ctx) + sep;
            // Print out the directory listing 
            text += "DIRECTORY CONTENTS: " + getFileList(ctx) + sep;
        } catch (Exception e) {
            // If there is an exception, show it in the TextView
            text += sep + e + sep;
        }
        text += "END";
        tv.setText(text);
    }

    private String getText(String filename, Context ctx) {
        String text = sep + "FILE " + filename + " CONTENT = ";
           try {
               InputStream is = ctx.getAssets().open(filename);
               // We guarantee that the available method returns 
               // the total size of the asset not more than 2G.
               int size = is.available();
               // Read the entire asset into a local byte buffer.
               byte[] buffer = new byte[size];
               is.read(buffer);
               is.close();
               text += " " + size + " " + new String(buffer);
           } catch (Exception e) {
               text += "EXCEPTION " + e;
           }
           text += getFileList(ctx);
           return text + sep;
    }

    private String getFileList(Context ctx) {
        String text = sep + "FILE LIST = " + sep;
        try {
            String[] l = ctx.getAssets().list("txt");
            for (String s : l) {
                text += s + ", ";
            }
        } catch (Exception e) {
            text += sep + "EXCEPTION: " + e + sep;
        }
        return text + sep;
    }
}

Now build each of the projects, and export them as Android Applications (right-click on the project name in Navigator view, then choose Export > Export Android Application and follow the instructions). In the final step, install both apps on your Android device. You should see MainCrossword appear in your list of apps. Open it, and you should see the following output:

Tada! The data from AnimalCrosswordPack has been read by the MainCrossword app. You’ve successfully shared data from one app to another.

You can also verify that the AnimalCrosswordPack doesn’t show up as a launchable app. You can’t open it.

Comments