3

I have a TabActivity with 3 tabs. There is an async task that when run by clicking a menu item for refresh, retrieves updated data from the server. This data is stored in the controller and is accessed by all views, so that the model only needs to be loaded once.

My problem is that after the async activity runs and the model is updated, how do I signal all three tabs to update their content?

My activity

public class DashboardActivity extends TabActivity {
    private ProfileModel profile;
    private TabHost tabHost;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        profile = Controller.getProfile();

        this.setContentView(R.layout.dashboard);

        tabHost = getTabHost();
        setupTab(new TextView(this), "Home", new Intent().setClass(DashboardActivity.this, HomeActivity.class));
        setupTab(new TextView(this), "History", new Intent().setClass(DashboardActivity.this, PaymentsActivity.class));
        setupTab(new TextView(this), "My Wallet", new Intent().setClass(DashboardActivity.this, MyWalletActivity.class));

        tabHost.setCurrentTab(0);

        ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);
        actionBar.setTitle(profile.Name);

    }

    private void setupTab(final View view, final String tag, Intent content) {
        View tabview = createTabView(tabHost.getContext(), tag);
            TabSpec setContent = tabHost.newTabSpec(tag)
                .setIndicator(tabview)
                .setContent(content);
            tabHost.addTab(setContent);
    }

    private static View createTabView(final Context context, final String text) {
        View view = LayoutInflater.from(context).inflate(R.layout.tabs_bg, null);
        TextView tv = (TextView) view.findViewById(R.id.tabsText);
        tv.setText(text);
        return view;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.mainmenu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.menu_settings:

            return true;
        case R.id.menu_refresh:
            new RefreshDashboardTask().execute();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }


    private class RefreshDashboardTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected void onPreExecute()
        {
            ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);

            if(actionBar != null)
                actionBar.setProgressBarVisibility(View.VISIBLE);
        }

        @Override
        protected Void doInBackground(Void... params) 
        {
            try {
                profile = DataHelper.getProfile();
                Controller.setProfile(profile);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (HttpException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ServerException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result)
        {
            ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);

            if(actionBar != null)
                actionBar.setProgressBarVisibility(View.GONE);
        }
    }
}

EDIT For further elaboration, here is some more code.

My controller

public class Controller extends Application {
    private static Controller instance;
    private static DefaultHttpClient client;
    private static ProfileModel profile;

    public Controller() {
        instance = this;
        client = new DefaultHttpClient();
        profile = null;
    }

    public static Context getContext() {
        return instance;
    }

    public static DefaultHttpClient getHttpContext() {
        return client;
    }

    public static ProfileModel getProfile() {
        return profile;
    }

    public static void setProfile(ProfileModel profile) {
        Controller.profile = profile;
    }

    @Override
    public void onCreate() 
    {
        super.onCreate();

    }
}

And one of my activities that is inside the tab view. This one is the simplest one, as it is just a single list. The home view is 2 lists, separated by headers, and the wallet view is dynamically generated lists with headers, created from a collection within a collection.

public class PaymentsActivity extends Activity {
    ProfileModel profile;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.setContentView(R.layout.payment_history);

        profile = Controller.getProfile();

        ListView itemList = (ListView)this.findViewById(R.id.payment_history_list);
        itemList.setTextFilterEnabled(true);
        itemList.clearChoices();
        itemList.setAdapter(new ItemSummaryAdapter(PaymentsActivity.this, R.layout.list_item_payment, profile.Items));
        //statementList.setOnItemClickListener(clickListener);
    }
}

The goal here is that the refresh button updates the data in the controller. All my views inside the tabs are updated.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Josh
  • 16,286
  • 25
  • 113
  • 158

2 Answers2

3

UPDATED:

If I were you I would the Observer pattern. First, add a set of listeners to your Controller class:

public class Controller extends Application {
    private static Controller instance;
    private static DefaultHttpClient client;
    private static ProfileModel profile;
    private static Set<ControllerUpdateListener> updateListeners = new HashSet<ControllerUpdateListener>();

    //...

    public static void addListener(ControllerUpdateListener listener)
    {
        updateListeners.add(listener);
    }

    public static interface ControllerUpdateListener {
        void onControllerUpdate(ProfileModel model);
    }
}

Then have your individual tab activities implement ControllerUpdateListener. Finally, add the trigger to the Controller class:

public static void setProfile(ProfileModel profile) {
    Controller.profile = profile;

    for(ControllerUpdateListener l : updateListeners) {
        l.onControllerUpdate(profile);
    }
}

Don't forget to register each activity as a Listener with the Controller:

public class PaymentsActivity extends Activity implements Controller.ControllerUpdateListener {
    ProfileModel profile;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.setContentView(R.layout.payment_history);

        Controller.addListener(this); // <-- Don't forget this...
        profile = Controller.getProfile();

        ListView itemList = (ListView)this.findViewById(R.id.payment_history_list);
        itemList.setTextFilterEnabled(true);
        itemList.clearChoices();
        itemList.setAdapter(new ItemSummaryAdapter(PaymentsActivity.this, R.layout.list_item_payment, profile.Items));
        //statementList.setOnItemClickListener(clickListener);
    }

    public void onControllerUpdate(ProfileModel model) {
        //update these views...
    }
}

Now each of your individual tab activities should trigger their notifyDataSetChanged() (or whatever other method triggers their update) in the onControllerUpdate(ProfileModel) method.

nicholas.hauschild
  • 42,483
  • 9
  • 127
  • 120
  • I know where to put the code. My problem is getting the tab views themselves to update with the new content. – Josh Jun 02 '11 at 19:39
  • I did something very similar. My only difference is that each activity registers a listener, and when the data in the controller is changed, the controller handles firing the event if changes are detected, and if the previous version wasn't null. Good to know I was on the right track! – Josh Jun 06 '11 at 13:19
2

Are you calling notifyDataSetChanged() on your adapter after refreshing the content in your views? If its a ListView then just notifying the adapter should trigger a refresh. This in fact is the only way you should be dealing with rows inside ListView.

Abhinav
  • 38,516
  • 9
  • 41
  • 49
  • How do I trigger that call from the activity housing the tabhost - the menu is in the same activity as the tabhost. I tried using an event, but I you can't modify a view unless it is in the same thread. I don't want to have to duplicate this code across all my tabs. – Josh Jun 02 '11 at 20:01
  • @Josh: Abhinav is on the right track. In particular you need to make use of onResume() in each of the tab content Activities. Each time onResume() is called, have the Activities check some flag to see if updates have been made and then get them to use 'notifyDataSetChanged()'. – Squonk Jun 02 '11 at 20:02
  • that would work when switching between tabs, but what about the currently open tab? – Josh Jun 02 '11 at 20:08
  • Can you get a reference to the currently open tab (using getCurrentTab()) and then call a function within that activity to call notifyDataSetChanged? A better way to do this would be to have your TabActivity display Views rather than having separate activities. – Abhinav Jun 03 '11 at 12:02