I wrote a tutorial about Smart Lists yesterday that showed you how to make your Lists smarter and, in the Addendum, I gave you a short example of how this design pattern can make Smart Objects that inform your activities whenever they are changed. In this tutorial, I am not going to duplicate that exactly… however, I wanted to provide a working example of just how powerful this method can be. How many times have you wanted to pass complex objects between Activities and found yourself hating Parcelables and Bundles because of their type limitations? How many times have you struggled to save complex objects during the application life cycle (orientation changes, etc). Below is an example that shows exactly how these Observable Singleton Objects can save you code, complexity and giant headaches. I have extended my previous example by duplicating the SmartAdapterExample.java, adding a separate array, and placing these two activities inside of a TabHost… so that you can see exactly how you can use this method to share objects across Activities without all of the voodoo I have seen in examples all over the web. I hope this helps you as much as it has me.

/BeyondSmartListAdapter/res/layout/tabs.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@android:id/tabhost"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent">
    <LinearLayout
       android:orientation="vertical"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:padding="5dp">
        <TabWidget
           android:id="@android:id/tabs"
           android:layout_width="fill_parent"
           android:layout_height="wrap_content" />
        <FrameLayout
           android:id="@android:id/tabcontent"
           android:layout_width="fill_parent"
           android:layout_height="fill_parent"
           android:padding="5dp" />
    </LinearLayout>
</TabHost>

/BeyondSmartListAdapter/res/layout/list1.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/base"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:orientation="vertical">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="center_horizontal">
        <Button
            android:id="@+id/button"
            android:text="Add Item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center">
        </Button>
        <Button
            android:id="@+id/clear"
            android:text="Clear List"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center">
        </Button>
    </LinearLayout>
    <ListView android:id="@android:id/list"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:layout_weight="1"
       android:drawSelectorOnTop="false" />
</LinearLayout>

/BeyondSmartListAdapter/res/layout/list2.xml (NOTE: I know I could have used the same layout as above and just set the color programmatically… it’s an example… what do you want?)

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
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/base"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:orientation="vertical"
   android:background="#2f2f4f">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="center_horizontal">
        <Button
            android:id="@+id/button"
            android:text="Add Item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center">
        </Button>
        <Button
            android:id="@+id/clear"
            android:text="Clear List"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center">
        </Button>
    </LinearLayout>
    <ListView android:id="@android:id/list"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:layout_weight="1"
       android:drawSelectorOnTop="false" />
</LinearLayout>

/BeyondSmartListAdapter/res/layout/list_item.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content"
   android:layout_height="40dip"
   android:textSize="18dip"
   android:textStyle="bold"
   android:textColor="#FFFFE0" />

/BeyondSmartListAdapter/src/com/androidworkz/smartlistadapter/SmartList.java

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.androidworkz.smartlistadapter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Observable;

public class SmartList extends Observable implements List<String> {

    List<String> delegate;

    public SmartList(List<String> delegate) {
        this.delegate = delegate;
    }

    private SmartList() {
        this.delegate = new ArrayList<String>();
    }

    public static synchronized SmartList getInstance() {
        if (instance == null)
            instance = new SmartList();
        return instance;
    }

    private static SmartList instance;
   
    @Override
    public String toString() {
        return delegate.toString();
    }

    @Override
    public boolean add(String song) {
        boolean result = delegate.add(song);
        if (result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public void add(int position, String song) {
        delegate.add(position, song);
        setChanged();
        notifyObservers();
    }

    @Override
    public boolean addAll(Collection<? extends String> c) {
        boolean result = delegate.addAll(c);
        if(result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public boolean addAll(int position, Collection<? extends String> c) {
        boolean result = delegate.addAll(position, c);
        if(result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public void clear() {
        delegate.clear();
        setChanged();
        notifyObservers();
    }

    @Override
    public boolean contains(Object song) {
        return delegate.contains(song);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return delegate.containsAll(c);
    }

    @Override
    public String get(int position) {
        return delegate.get(position);
    }

    @Override
    public int indexOf(Object song) {
        return delegate.indexOf(song);
    }
   
    @Override
    public boolean equals(Object song) {
        return delegate.equals(song);
    }
   
    @Override
    public int hashCode() {
        return delegate.hashCode();
    }

    @Override
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    @Override
    public Iterator<String> iterator() {
        return delegate.iterator();
    }

    @Override
    public int lastIndexOf(Object song) {
        return delegate.lastIndexOf(song);
    }

    @Override
    public ListIterator<String> listIterator() {
        return delegate.listIterator();
    }

    @Override
    public ListIterator<String> listIterator(int position) {
        return delegate.listIterator(position);
    }

    @Override
    public String remove(int position) {
        String result = delegate.remove(position);
        setChanged();
        notifyObservers();
        return result;
    }

    @Override
    public boolean remove(Object song) {
        boolean result = delegate.remove(song);
        if (result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean result = delegate.removeAll(c);
        if (result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean result = delegate.retainAll(c);
        if (result) {
            setChanged();
        }
        notifyObservers();
        return result;
    }

    @Override
    public String set(int position, String song) {
        String result = delegate.set(position, song);
        setChanged();
        notifyObservers();
        return result;
    }

    @Override
    public int size() {
        return delegate.size();
    }

    @Override
    public List<String> subList(int fromPosition, int toPosition) {
        return delegate.subList(fromPosition, toPosition);
    }

    @Override
    public Object[] toArray() {
        return delegate.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return delegate.toArray(a);
    }
}

/BeyondSmartListAdapter/src/com/androidworkz/smartlistadapter/Tabs.java

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
package com.androidworkz.smartlistadapter;

import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TabHost;

public class Tabs extends TabActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tabs);

        TabHost tabHost = getTabHost();  // The activity TabHost
        TabHost.TabSpec spec;  // Resusable TabSpec for each tab
        Intent intent;  // Reusable Intent for each tab

        // Create an Intent to launch an Activity for the tab (to be reused)
        intent = new Intent().setClass(this, SmartListAdapterExample.class);

        // Initialize a TabSpec for each tab and add it to the TabHost
        spec = tabHost.newTabSpec("list1").setIndicator("List 1",
                          null)
                      .setContent(intent);
        tabHost.addTab(spec);

        // Do the same for the other tabs
        intent = new Intent().setClass(this, SmartListAdapterExample2.class);
        spec = tabHost.newTabSpec("list2").setIndicator("List 2",
                          null)
                      .setContent(intent);
        tabHost.addTab(spec);

        tabHost.setCurrentTab(0);

    }
}

/BeyondSmartListAdapter/src/com/androidworkz/smartlistadapter/SmartListAdapterExample.java

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package com.androidworkz.smartlistadapter;

import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import android.app.ListActivity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

public class SmartListAdapterExample extends ListActivity {

    private static final int EXIT = 0;
    private LayoutInflater inflater;
    SmartList smartList1;
    String[] colors;
    String[] cars;
    Random random;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        Resources resources = getResources();
        colors = resources.getStringArray(R.array.colors);
        cars = resources.getStringArray(R.array.cars);
       
        random = new Random();
       
        smartList1 = SmartList.getInstance();
       
        int x = 0;
        while(x < 5) {
            int rndcolor = random.nextInt(colors.length - 1);
            int rndcar = random.nextInt(cars.length - 1);
            smartList1.add(colors[rndcolor]+" "+cars[rndcar]);
            x++;
        }
       
        SmartListAdapter smartAdapter = new SmartListAdapter(this);
       
        inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.list1, (ViewGroup) findViewById(R.id.base));

        ListView list = (ListView) layout.findViewById(android.R.id.list);     
        list.setAdapter(smartAdapter);
       
        Button addItem = (Button) layout.findViewById(R.id.button);
        Button clearList = (Button) layout.findViewById(R.id.clear);
       
        addItem.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                int rndcolor = random.nextInt(colors.length - 1);
                int rndcar = random.nextInt(cars.length - 1);
                smartList1.add(colors[rndcolor]+" "+cars[rndcar]);
                Log.v("SmartList", "Count is :"+smartList1.size());
            }
           
        });
       
        clearList.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                smartList1.clear();
            }
           
        });

        setContentView(layout);
       
    }
   
    private class SmartListAdapter extends BaseAdapter implements Observer {
        private LayoutInflater mInflater;
        private SmartList smartList2;
       
        class ViewHolder {
            TextView text;
        }

        public SmartListAdapter(Context context) {
            mInflater = LayoutInflater.from(context);                  
            smartList2 = SmartList.getInstance();
            smartList2.addObserver(this);
        }

        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.text.setText(smartList2.get(position));
            return holder.text;
        }        

        public int getCount() {
            return smartList2.size();
        }

        public Object getItem(int position) {
            return smartList2.get(position);
        }

        public long getItemId(int position) {
            return position;
        }

        @Override
        public void update(Observable observable, Object object) {
            if (observable.equals(SmartList.getInstance())) {
                Log.v("SmartList.update", "Yep... it's me!");
            }
            smartList2 = SmartList.getInstance();
            notifyDataSetChanged();
            Log.v("SmartListAdapter", "Count is :"+smartList2.size());
        }
    }
   
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
       
        int NONE = Menu.NONE;
        menu.add(NONE, EXIT, 1, "Exit");
        return true;
    }
   
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case EXIT:
            quit();
            break;
        }

        return super.onOptionsItemSelected(item);
    }
   
    public void quit() {
        int pid = android.os.Process.myPid();
        android.os.Process.killProcess(pid);
        System.exit(0);
    }
}

/BeyondSmartListAdapter/src/com/androidworkz/smartlistadapter/SmartListAdapterExample2.java

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.androidworkz.smartlistadapter;

import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import android.app.ListActivity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

public class SmartListAdapterExample2 extends ListActivity {

    private static final int EXIT = 0;
    private LayoutInflater inflater;
    SmartList smartList1;
    String[] houses;
    String[] materials;
    Random random;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        Resources resources = getResources();
        houses = resources.getStringArray(R.array.houses);
        materials = resources.getStringArray(R.array.materials);
       
        random = new Random();
       
        smartList1 = SmartList.getInstance();
       
        SmartListAdapter smartAdapter = new SmartListAdapter(this);
       
        inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.list2, (ViewGroup) findViewById(R.id.base));

        ListView list = (ListView) layout.findViewById(android.R.id.list);     
        list.setAdapter(smartAdapter);
       
        Button addItem = (Button) layout.findViewById(R.id.button);
        Button clearList = (Button) layout.findViewById(R.id.clear);
       
        addItem.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                int rndcolor = random.nextInt(houses.length - 1);
                int rndcar = random.nextInt(materials.length - 1);
                smartList1.add(houses[rndcolor]+" "+materials[rndcar]);
                Log.v("SmartList", "Count is :"+smartList1.size());
            }
           
        });
       
        clearList.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                smartList1.clear();
            }
           
        });

        setContentView(layout);
       
    }
   
    private class SmartListAdapter extends BaseAdapter implements Observer {
        private LayoutInflater mInflater;
        private SmartList smartList2;
       
        class ViewHolder {
            TextView text;
        }

        public SmartListAdapter(Context context) {
            mInflater = LayoutInflater.from(context);                  
            smartList2 = SmartList.getInstance();
            smartList2.addObserver(this);
        }

        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.text.setText(smartList2.get(position));
            return holder.text;
        }        

        public int getCount() {
            return smartList2.size();
        }

        public Object getItem(int position) {
            return smartList2.get(position);
        }

        public long getItemId(int position) {
            return position;
        }

        @Override
        public void update(Observable observable, Object object) {
            if (observable.equals(SmartList.getInstance())) {
                Log.v("SmartList.update", "Yep... it's me!");
            }
            smartList2 = SmartList.getInstance();
            notifyDataSetChanged();
            Log.v("SmartListAdapter", "Count is :"+smartList2.size());
        }
    }
   
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
       
        int NONE = Menu.NONE;
        menu.add(NONE, EXIT, 1, "Exit");
        return true;
    }
   
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case EXIT:
            quit();
            break;
        }

        return super.onOptionsItemSelected(item);
    }
   
    public void quit() {
        int pid = android.os.Process.myPid();
        android.os.Process.killProcess(pid);
        System.exit(0);
    }
}

Download the complete Eclipse project here