Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to use the same Drawer object in different activities? #1385

Closed
acosti opened this issue Jul 12, 2016 · 27 comments
Closed

Is it possible to use the same Drawer object in different activities? #1385

acosti opened this issue Jul 12, 2016 · 27 comments
Assignees
Labels

Comments

@acosti
Copy link

acosti commented Jul 12, 2016

Hello,

I'm getting an error if I attempt to hold the Drawer object and display it more than once.
Is it possible, for performance considerations, to create exactly one (static) Drawer and use it in other activities?
I realize the best-practice would be doing one activity with fragments, but this is not the case.

Thanks
A Costi

@mikepenz
Copy link
Owner

@acosti if you can not use one activity with fragments (which is definitely the correct implementation) you will have to go a different approach.

There is definitely NO chance to have the drawer static.

What you can do is to have a helper class which creates the drawer for you (with the same logic) and you just call this helper in all your activities. But the drawer .build() has to be called in each of those activities otherwise the drawer is not added.

@mikepenz mikepenz self-assigned this Jul 12, 2016
@acosti
Copy link
Author

acosti commented Jul 12, 2016

Ok, so my question would be:
Is there a way for said helper class to only create the drawer once, but .build() it multiple times at different stages of the app?

The rational stems from the fact that it is not cheap for me to create the drawer, as it is highly customized and interacts with various other components of the app. So recreating it each time from scratch would be suicidal in terms of performance.

@mikepenz
Copy link
Owner

@acosti yes that's possible. You can keep the DrawerBuilder global once. And call the build() per activity then, as this will initiate the inflating of the layouts into your app in the end.

@acosti
Copy link
Author

acosti commented Jul 13, 2016

Ok mike, thanks. Appreciate the help.

@acosti
Copy link
Author

acosti commented Jul 13, 2016

excuse me Mike but it does not seem to work.

I am getting an explicit runtime error:
"you must not reuse a DrawerBuilder builder" .. gotta admit, that's one of the more informative error messages. Any ideas?

@mikepenz mikepenz reopened this Jul 14, 2016
@mikepenz
Copy link
Owner

@acosti oh I forgot about this behavior as I had to prevent people calling .build() multiple times (which would result in them having multiple drawers in the same activity)

The easiest solution would be that you just keep the "expensive" information global and just rebuild the drawer as normal. I assume getting the items is expensive for you so just keep those items globally or so?

@acosti
Copy link
Author

acosti commented Jul 14, 2016

yeah I guess that's the sane solution for my crazy problem.
if you happen to think of a better one for holding the whole Drawer object I'd love to know.

either way, thanks for the swift responses. your library has saved me tons of time.

@mikepenz
Copy link
Owner

@acosti you could do a pull request and add a method reset to the DrawerBuilder which sets the mUsed field back to false.

https://github.com/mikepenz/MaterialDrawer/blob/develop/library/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java#L71

this would then allow you to do this. And it will still prevent reusing the DrawerBuilder for people who are not quite sure about what they are doing

@mikepenz
Copy link
Owner

@acosti created a new version v5.3.5 which allows this.
https://github.com/mikepenz/MaterialDrawer/releases/tag/v5.3.5

@acosti
Copy link
Author

acosti commented Jul 17, 2016

Hello,

I have tried to use the .reset() method you supplied but I'm facing issues.
Here's the error I'm getting.

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

What I did was:

  1. Create a DrawerBuilder
  2. Save the DrawerBuilder as a static variable
  3. Build() the DrawerBuilder on Activity A
  4. When loading Activity B - reset() the DrawerBuilder
  5. Let the DrawerBuilder know the new activity - .withActivity(Activity B)
  6. Build() the DrawerBuilder on Activity B

And then it crashed.

@mikepenz
Copy link
Owner

@acosti I think this may be related to the DrawerLayout being reused:
#1399 (comment)

Can you try to also call withDrawerLayout(-1) additionally to withActivity

@acosti
Copy link
Author

acosti commented Jul 17, 2016

Holy shit that was fast. Thanks man, really.

Same result unfortunately. Let me paste the whole log here.

FATAL EXCEPTION: main
                                                                                               Process: com.example.agombiprototypetest.prototypetest, PID: 20689
                                                                                               java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.agombiprototypetest.prototypetest/com.example.agombiprototypetest.prototypetest.Prototypes.core.aboutUs}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
                                                                                                   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                                                                   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                                                                   at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                                                                   at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                                                   at android.os.Looper.loop(Looper.java:148)
                                                                                                   at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)`

`Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
                                                                                                   at android.view.ViewGroup.addViewInner(ViewGroup.java:4309)
                                                                                                   at android.view.ViewGroup.addView(ViewGroup.java:4145)
                                                                                                   at android.view.ViewGroup.addView(ViewGroup.java:4117)
                                                                                                   at com.mikepenz.materialdrawer.DrawerBuilder.createContent(DrawerBuilder.java:1629)
                                                                                                   at com.mikepenz.materialdrawer.DrawerBuilder.buildView(DrawerBuilder.java:1483)
                                                                                                   at com.mikepenz.materialdrawer.DrawerBuilder.build(DrawerBuilder.java:1281)
                                                                                                   at com.example.agombiprototypetest.prototypetest.BL.Navigation.DrawerNavigationModel.BuildDrawerWithToolbar(DrawerNavigationModel.java:119)
                                                                                                   at com.example.agombiprototypetest.prototypetest.BL.Navigation.DrawerNavigationModel.injectView(DrawerNavigationModel.java:96)
                                                                                                   at com.example.agombiprototypetest.prototypetest.BL.Models.AgombiNavActivity.onCreate(AgombiNavActivity.java:37)
                                                                                                   at android.app.Activity.performCreate(Activity.java:6237)
                                                                                                   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
                                                                                                   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
                                                                                                   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
                                                                                                   at android.app.ActivityThread.-wrap11(ActivityThread.java) 
                                                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
                                                                                                   at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                                                   at android.os.Looper.loop(Looper.java:148) 
                                                                                                   at android.app.ActivityThread.main(ActivityThread.java:5417) 
                                                                                                   at java.lang.reflect.Method.invoke(Native Method) 

@acosti
Copy link
Author

acosti commented Jul 17, 2016

I'm sure.

Here's the process:

  1. Activity A needs a Drawer
  2. DrawerBuilder prepares the drawer
  3. DrawerBuilder gets bound to Activity A via .withActivity(Activity A) and also resets the layout via .withDrawerLayout(-1)
  4. DrawerBuilder is built()
  5. Activity B needs a Drawer
  6. DrawerBuilder gets reset()
  7. DrawerBuilder gets bound to Activity B via withActivity(Activity B) and also resets the layout via .withDrawerLayout(-1)
  8. DrawerBuilder is built

@mikepenz
Copy link
Owner

mikepenz commented Jul 17, 2016

could you please send this steps as sample so I can debug it? thanks

@acosti
Copy link
Author

acosti commented Jul 18, 2016

hmm that's gonna be a bit tough. let me try to pull something together for you to see.

@acosti
Copy link
Author

acosti commented Jul 18, 2016

Ok, this will be tricky. Please bear with me and I'll be glad to respond to any question ASAP.

Each activity that contains a Drawer uses the following code:

private void initDrawerForActivity(Context activityContext)
    {
        // Get the activity
        AgombiNavActivity activity = (AgombiNavActivity) activityContext;

        if (sSharedDrawer == null) {
            sSharedDrawer =generateDrawer(activityContext);
        }
        else {
            sSharedDrawer.reset();
        }

        sSharedDrawer.withActivity(activity)
                     .withDrawerLayout(-1);
    }

The drawer generation code of generateDrawer(activityContext) is as follows:

private DrawerBuilder generateDrawer(Context context) {

        // Get the menu items
        RealmResults<AgombiComponentRealm> menuItems =
                NavigationManager.getInstance().getMenu();

        List<IDrawerItem> drawerItems = new LinkedList<>();

        for (final AgombiComponentRealm menuItem : menuItems) {

        // Get the drawer item externally
            PrimaryDrawerItem item =
                    AgombiViewLoader.getInstance().initializeMenuItem(context, menuItem);
            drawerItems.add(item);
        }

       // Get the footer externally
        ViewGroup footer = AgombiViewLoader.getInstance().initializeDrawerFooter(context);

        DrawerBuilder drawerBuilder = new DrawerBuilder()
                .withTranslucentStatusBar(false)
                .withDelayDrawerClickEvent(300)
                .withDrawerItems(drawerItems)
                .withFooter(footer)
                .withFooterDivider(false);
        // TODO: turn footer to sticky (removing its divider as sticky doesn't work - attempt to solve)

        return drawerBuilder;
    }

Now, the activity calls for the build function. But it isn't so simple.
Each activity in my project is responsible for its own navigation, so an activity might be with a drawer but without a toolbar. Or with a toolbar. So I gotta check for this first.
Following is the code that checks whether a toolbar is required or not, and accordingly, invokes the DrawerBuilder's build().

public void injectView(ViewGroup activityLayout) {

        Drawer projectedDrawer = null;
        Toolbar potentialToolbar = getToolbar();
        // Check whether a toolbar is required. If so, build a combined Drawer + Toolbar
        if (potentialToolbar == null)
        {
            projectedDrawer = sSharedDrawer.build();
        }
        else
        {
            projectedDrawer = BuildDrawerWithToolbar(sSharedDrawer, activityLayout, getToolbar());
        }
    }

And the following is the extra code that builds the Drawer given a Toolbar...

private Drawer BuildDrawerWithToolbar(DrawerBuilder drawerBuilder, ViewGroup activityLayout, Toolbar optionalToolbar) {

        AttachToolbarToLayout(activityLayout, optionalToolbar);

        // Create the drawer
        return drawerBuilder
                .withToolbar(optionalToolbar)
                .withActionBarDrawerToggle(true)
                .withActionBarDrawerToggleAnimated(true)
                .build();
    }

Damn.
I hope this is clear enough.
Sorry, my code is a bit complex. I have a huge task to accomplish but my knowledge in Android is a WIP, haha. Hope the code is fine :)

Let me know if you can catch anything, Mike.
Appreciate the help and swift responses, really.

@acosti
Copy link
Author

acosti commented Jul 18, 2016

Updated the formatting for easier access

@mikepenz mikepenz reopened this Jul 18, 2016
@caralin3
Copy link

caralin3 commented Jul 21, 2016

@mikepenz How would I use fragments to do this? I'm not that familiar with fragments.

@mikepenz
Copy link
Owner

@acosti sorry for the late answer. It's quite complex the above. Perhaps posting a "zip" containing the simple sample code would be easier

@mikepenz
Copy link
Owner

@caralin3 if you have one activity with fragments. Just use the listener and change out the fragments if the user selects a new one

@caralin3
Copy link

@mikepenz Do you have a link that can help me create a fragment?

@mikepenz
Copy link
Owner

@caralin3 the official android documentation shows how to comit fragments to a container view
https://developer.android.com/guide/components/fragments.html

just use the listener to detect selections of the user, and switch out the fragment

@acosti
Copy link
Author

acosti commented Jul 24, 2016

hey @mikepenz,

I'm trying a new approach: instead of redrawing the drawer anew on each activity, and instead of having the "1 activity - many fragments" implementation:

I'll do 1 activity - many activities.
that is - there's one drawer activity, responsible for holding the drawer and drawing it ONCE.
and upon navigation - the setContentView has an override which gets the target layout, inflates it, and places it in the one-drawer-activity's layout in a "content frame".

So basically, same implementation I guess as you'd have with fragments except with activities and self-managed.

My question is, how would you approach such solution?

I was entertaining the idea of simply (I hope it's simple) overriding the drawer's layout in order to add the "content frame". from there it should be easy to do the layout inflation on setContentView's override. your thoughts?

@Rainer-Lang
Copy link

Soneone tried with Dagger2?

@mikepenz
Copy link
Owner

@acosti I remember there was a library which did exactly this. Keeping one activity but filling it up with different views depending on the context.

I am not quite sure if this is a good or the best approach. Android comes with a pretty good and feature rich concept of Fragments which will exactly do what you need.

It is also rather simple to split things up into fragments, and to handle fragments and doing the switching with the Drawer as you just need to commit the correct fragment when an item is selected. You can even move the whole fragment commiting logic into the Listener of the drawer, as you can trigger it after start, and also when the onCreate is called with onSavedInstanceState setting the correct fragment again.

@mikepenz
Copy link
Owner

Ok to finally close this issue. After some more digging and debugging and trying. It is not really possible to reuse the same DrawerBuilder, as it turns out that the RecyclerView is not happy if the Adapter is reused.

The optimal solution for this usecase is to remember the complex data you have to fetch, and then just build the drawer with the items, based on the already loaded data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants