Closed acosti closed 8 years ago
@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.
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.
@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.
Ok mike, thanks. Appreciate the help.
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?
@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?
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.
@acosti you could do a pull request and add a method reset
to the DrawerBuilder
which sets the mUsed
field back to false.
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
@acosti created a new version v5.3.5 which allows this. https://github.com/mikepenz/MaterialDrawer/releases/tag/v5.3.5
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:
.withActivity(Activity B)
And then it crashed.
@acosti I think this may be related to the DrawerLayout
being reused:
https://github.com/mikepenz/MaterialDrawer/issues/1399#issuecomment-233190027
Can you try to also call withDrawerLayout(-1)
additionally to withActivity
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 hmm it fails here: https://github.com/mikepenz/MaterialDrawer/blob/develop/library/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java#L1629
Weird enough as this one is inflated newly during .build()
here
https://github.com/mikepenz/MaterialDrawer/blob/develop/library/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java#L1469
are you sure you called it?
I'm sure.
Here's the process:
.withActivity(Activity A)
and also resets the layout via .withDrawerLayout(-1)
built()
reset()
withActivity(Activity B)
and also resets the layout via .withDrawerLayout(-1)
built
could you please send this steps as sample so I can debug it? thanks
hmm that's gonna be a bit tough. let me try to pull something together for you to see.
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.
Updated the formatting for easier access
@mikepenz How would I use fragments to do this? I'm not that familiar with fragments.
@acosti sorry for the late answer. It's quite complex the above. Perhaps posting a "zip" containing the simple sample code would be easier
@caralin3 if you have one activity with fragments. Just use the listener and change out the fragments if the user selects a new one
@mikepenz Do you have a link that can help me create a fragment?
@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
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?
Soneone tried with Dagger2?
@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 Fragment
s 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.
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.
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