0

Background:

I have a FragmentActivity that uses a DrawerLayout. I created a class called NavDrawerManager to abstract out the code for dealing with the drawer layout. To construct this object, I need to pass in and keep a reference to an Activity. I use this activity reference to call findViewById(), and I also use the activity as a context when creating a list adapter, etc. I keep a reference to a NavDrawerManager object in my activity so I can perform callbacks and operations on the drawer layout.

UPDATE: Per Xaver's suggestion, I switched to extend FragmentActivity into a NavDrawerActivity. Instead of having a NavDrawerManager object in my activity, the superclass handles the nav drawer code. See updated code.

Issue:

Everything works fine until I change orientations. After changing orientations, it appears that my NavDrawerActivity still references the old layout. I say this because I have a progress bar and a couple of buttons in the DrawerLayout that I am attempting to update, but they don't reflect the updates.

When I call Activity.findViewById() to get the progress bar and make it visible, it doesn't show any changes, nor do any of the changes to the buttons show. Note: Activity.findViewById() does not return null. However, if I do the same things before changing orientation, the progress bar and buttons show the appropriate changes.

Code:

NavDrawerActivity:

public class NavDrawerActivity extends LowProfileFragmentActivity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;

    private ExpandableListView mDrawerList;
    private NavDrawerAdapter mListAdapter;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        mDrawerToggle.onOptionsItemSelected(item);
        return super.onOptionsItemSelected(item);
    }

    //Called via subclass after setContentView    
    protected void setupNavDrawer() {
        initDrawerLayout();
        initDrawerList();
        setButton1();
        setButton2();
    }

    private void initDrawerLayout() {
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = getActionBarDrawerToggle();
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    private void initDrawerList() {
        mDrawerList = (ExpandableListView) mDrawerLayout
                .findViewById(android.R.id.list);

        mListAdapter = getNavDrawerAdapter();
    }

    private ActionBarDrawerToggle getActionBarDrawerToggle() {
        return new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_navigation_drawer, 0, 0) {

            public void onDrawerClosed(View view) {

            }

            public void onDrawerOpened(View view) {

            }
        };
    }


    public void setDrawerLoading(boolean loading) {
        ProgressBar progressBar = (ProgressBar) mDrawerLayout
                .findViewById(R.id.progress_bar);
        Button button1 = (Button) this.findViewById(R.id.button1);
        Button button2 = (Button) this
                .findViewById(R.id.button2);

        /* None of the below changes appear after changing orientation */
        if (loading) {
            button1.setEnabled(false);
            button2.setEnabled(false);
            progressBar.setVisibility(View.VISIBLE);
        } else {
            progressBar.setVisibility(View.GONE);
            button1.setEnabled(true);
            button2.setEnabled(true);
        }
    }

    private void setButton1() {
        Button button1 = (Button) this.findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //do something
            }

        });
    }

    private void setButton2() {
        Button button2 = (Button) this
                .findViewById(R.id.button2);
        button2.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //do something
            }

        });
    }
}

ExampleActivity:

public class ExampleActivity extends NavDrawerActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initUI();
    }

    private void initUI() {
        setContentView(R.layout.activity_sift);

        // Call the superclass method to set up the nav drawer
        setupNavDrawer();
    }
}

AndroidManifest.xml:

<activity
    android:name="com.example.app.ExampleActivity"
    android:label="@string/app_name"
    android:launchMode="singleTop"
    android:theme="@style/AppTheme" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
dennisdrew
  • 4,399
  • 1
  • 25
  • 25
  • Wouldn't it be easier if you implemeted a `NavDrawerActivity` instead of a `NavDrawerManager`? Generally things like this should be avoided. The UI should be handled by the `Fragments` or `Activities` themselves. – Xaver Kapeller Jun 23 '14 at 16:23
  • That could work. I guess my activity hierarchy is already getting pretty extensive...I have a BaseFragmentActivity > LowProfileFragmentActivity > then this activity. (Not shown in the code, as it has been stripped down). I figured making a LowProfileNavDrawerActivity was a bit over the top. – dennisdrew Jun 23 '14 at 16:28
  • No, if you create a dedicated `NavDrawerActivity` you should be fine. – Xaver Kapeller Jun 23 '14 at 16:30
  • But the problem is, I want to have a super class Activity that handles making the system UI go "low-profile" (hide the notification bar, etc.). If I make a dedicated NavDrawerActivity, I can't extend from LowProfileFragmentActivity, as it seems to violate some form of SRP to me...could be totally wrong. – dennisdrew Jun 23 '14 at 16:33
  • There is nothing wrong with your `NavDrawerActivity` extending the `LowProfileActivity`. I assume you mean Single Responsibility Principle when you say SRP? – Xaver Kapeller Jun 23 '14 at 16:37
  • Yep. I've been trying to keep my activity code (and all application code) pretty abstract so that I don't have a bunch of moving parts within one activity class. I figured having a NavDrawerActivity was a responsibility / purpose in and of itself, and to extend from LowProfileActivity would make it not a standalone NavDrawerActivity that can be implemented elsewhere. – dennisdrew Jun 23 '14 at 16:43
  • 1
    Well no, that would defeat the purpose of abstraction if you weren't allowed to reuse already existing implementations. A lot of abstraction is great, but when it is overdone it can become a problem. Don't focus so much on everything being perfectly abstracted and according to OOP standards. There is nothing wrong with an `Activity` having both a `NavigationDrawer` and hiding the notification bar. And if you already have an `Activity` that implements one of those two things you should extend it. Your only other option would be to reimplement the same behaviour in a second `Activity`. – Xaver Kapeller Jun 23 '14 at 16:50
  • I'm going to try that and then let you know how it goes. If you'd like, you can post an answer and get the credit. Otherwise, I'll go ahead and post an answer to the question. Thanks so much for the help. – dennisdrew Jun 23 '14 at 18:42

1 Answers1

1

Wouldn't it be easier if you implemented a NavDrawerActivity instead of a NavDrawerManager? Generally things like this should be avoided. The UI should be handled solely by the Fragments or Activities themselves.

I understand why you would want to create such a NavDrawerManager, simply to make the whole thing reusable and a lot of abstraction is great, but when it is overdone it can become a problem. Don't focus so much on everything being perfectly abstracted and according to OOP standards. There is nothing wrong with an Activity having both a NavigationDrawer and hiding the notification bar. And if you already have an Activity that implements one of those two things you should extend it. Your only other option would be to reimplement the same behaviour in a second Activity and that would pretty much defeat the purpose of abstraction.


EDIT: I have refactored and improved your code, you really should not have so many different methods for everything, all the setup belongs in onCreate(), that you write extra methods for each step just introduces the possibility for additional errors. Try this:

public abstract class NavDrawerActivity extends LowProfileFragmentActivity {

    private ActionBarDrawerToggle drawerToggle;
    private DrawerLayout drawerLayout;
    private ProgressBar progressBar;
    private Button button1;
    private Button button2;

    private ExpandableListView drawerList;
    private NavDrawerAdapter drawerListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayout());

        this.drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        this.drawerToggle = new ActionBarDrawerToggle(this, this.drawerLayout, R.drawable.icon_drawer, R.string.drawer_open, R.string.drawer_close);
        this.drawerLayout.setDrawerListener(this.drawerToggle);

        this.drawerList = (ExpandableListView) this.drawerLayout.findViewById(android.R.id.list);
        this.progressBar = (ProgressBar) this.drawerLayout.findViewById(R.id.progress_bar);

        this.button1 = (Button) findViewById(R.id.button1);
        this.button1.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //do something
            }
        });

        this.button2 = (Button) findViewById(R.id.button2);
        this.button2.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //do something
            }
        });

        this.drawerListAdapter = new NavDrawerAdapter(...);
        this.drawerList.setAdapter(this.drawerListAdapter);
    }

    protected abstract int getLayout();

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        this.drawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        this.drawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return this.drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
    }

    public void setDrawerLoading(boolean loading) {
        this.button1.setEnabled(!loading);
        this.button2.setEnabled(!loading);
        this.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
    }
}

As you can see I use an abstract method called getLayout() to pass the correct layout to setContentView(). getLayout() has to be implemented by all Activities which extend the NavDrawerActivity therfore giving the sub Activities control over the used layout.

As such your ExampleActivity should look like this:

public class ExampleActivity extends NavDrawerActivity {

    @Override
    protected int getLayout() {
        return R.layout.activity_sift;
    }
}

I tested everything and it's working for me.

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
  • So, strangely enough, I changed to make a NavDrawerActivity and it *still* behaves the same way...really not sure what's going on here. – dennisdrew Jun 24 '14 at 14:36
  • Could you update your code? I will take a look. Please also post your manifest, I think I know what might be wrong. – Xaver Kapeller Jun 24 '14 at 14:39
  • Okay, I updated my code. Let me know what you think. – dennisdrew Jun 24 '14 at 15:23
  • I updated my answer. If you have any further questions feel free to ask. – Xaver Kapeller Jun 24 '14 at 15:57
  • Did my answer help you or is there still a problem? If it isn't working with the code I posted above then the problem most likely has nothing to do with the `NavDrawerActivity`. – Xaver Kapeller Jun 24 '14 at 21:30
  • Sorry for the delay...been a bit busy with other projects. I will get back to you sometime between today and tomorrow. Thanks so much for your help! – dennisdrew Jun 25 '14 at 15:17
  • Still didn't seem to work...do you think it has to do with my activity having launchMode as singleTop? – dennisdrew Jul 03 '14 at 14:32
  • I don't think so, there probably is something wrong with the `LowProfileFragmentActivity`. – Xaver Kapeller Jul 03 '14 at 14:34
  • Okay. I think you're on the right track. I'll try to investigate more. Apologies for the delays...this little bug isn't the top priority in the project, so I haven't been able to devote a ton of time to it. – dennisdrew Jul 03 '14 at 14:36
  • It's no problem at all, just take as much time as you need. – Xaver Kapeller Jul 03 '14 at 14:41
  • Solved this problem? I'm the same way. After the rotation I try to call a getActivity (). Invalidateoptionsmenu () and does not work. – extmkv Sep 12 '14 at 16:16