square / mortar

A simple library that makes it easy to pair thin views with dedicated controllers, isolated from most of the vagaries of the Activity life cycle.
Apache License 2.0
2.16k stars 155 forks source link

ViewPager's PagerAdapter & Mortar's Screens #162

Open ksloginov opened 9 years ago

ksloginov commented 9 years ago

Hi,

This topic has been discussed a while ago and even nice snippet has been posted (https://gist.github.com/kboyarshinov/d30c354541f95ecaeaac), but since then, the Flow and Mortar have changed and I'm a bit confused.

It used to be something like this:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
    Path screen = screens[position];
        MortarScope originalScope = MortarScope.getScope(context);
        MortarScope newChildScope =  originalScope.requireChild(screen);
        Context childContext = newChildScope.createContext(context);
        View newChild = Layouts.createView(childContext, screen);
        container.addView(newChild);
        return newChild;
    }

But now, Layouts is no longer part of Flow and requireChild is no longer part of MortarScope.

Does anyone succeed in adapting it for the latest libs versions?

Thanks a lot(all square libraries are so awesome!) & best wishes!

ksloginov commented 9 years ago

Ok, I didn't succeed but, probably someone can guide me from this point:

The initial Dagger Registration:

@Module(
        injects = {
                MainActivity.class,
        },
        library = true,
        complete = false
)
public class DaggerConfig {
    @SuppressWarnings("unused")
    @Provides @Singleton Gson provideGson() {
        return new GsonBuilder().create();
    }
}

MainScreen, whose View is hosting ViewPager:

@Layout(R.layout.screen_main) @WithModule(MainScreen.Module.class)
public class MainScreen extends Path {
    @dagger.Module(injects = MainView.class, addsTo = DaggerConfig.class)
    public static class Module {}

    @Singleton
    public static class Presenter extends ViewPresenter<MainView> {
        @Inject
        public Presenter() {}
    }
}

MainView:

...........
    @Inject
    MainScreen.Presenter presenter;
...........
    @Override protected void onFinishInflate() {
        super.onFinishInflate();
        ButterKnife.inject(this);

        final Path[] screens = {
                new SubScreen("1"),
                new SubScreen("2"),
                new SubScreen("3"),
        };

        CustomPagerAdapter customPagerAdapter = new CustomPagerAdapter(getContext(), screens );
        customPagerAdapter .setAdapter(firstRunPagerAdapter);
    }
.....

Now, the main part, SubScreen (3 similar screens, that differs only by the parameters we are passing into them => they should adjust views according these parameters)

@Layout(R.layout.screen_subscreen) @WithModule(SubScreen.Module.class)
public class SubScreen extends Path {

    private final String title;

    public SubScreen(String titleParam) {
        title = titleParam;
    }

    @dagger.Module(injects = SubView.class, addsTo = DaggerConfig.class)
    public class Module {

        @Provides
        SubViewMetadata provideSubViewMetadata() {
            return new SubViewMetadata(backgroundColor, title);
        }

    }

    @Singleton
    public static class Presenter extends ViewPresenter<SubView> {

        private String title;

        @Inject
        public Presenter(String title) {
            this.title= title;
        }

        @Override
        protected void onLoad(Bundle savedInstanceState) {
            super.onLoad(savedInstanceState);
            if (!hasView()) {
                return;
            }

            getView().setTitle(subViewMetadata.title);
        }
    }
}

and it's view

public class SubView extends FrameLayout {

    @InjectView(R.id.subViewTitleTextView)
    TextView subViewTitleTextView;

    @Inject
    SubScreen.Presenter presenter;

    public SubView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ObjectGraphService.inject(context, this);
    }

    public void setTitle(String title) {
        subViewTitleTextView.setText(title);
    }

    @Override protected void onAttachedToWindow() {....}

    @Override protected void onDetachedFromWindow() {....}
......
}

Custom Pager adapter:

public class CustomPagerAdapter extends PagerAdapter {

    private final Context context;
    private final Path[] screens;

    public CustomPagerAdapter(Context context, Path[] screens) {
        this.context = context;
        this.screens = screens;
    }

    @Override
    public int getCount() {
        return (screens == null)? 0 : screens.length;
    }

    @Override
    public boolean isViewFromObject(View view, Object o) {
        return view.equals(o);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        Path screen = screens[position];
        MortarScope originalScope = MortarScope.getScope(context);
        MortarScope newChildScope =  originalScope.buildChild().build("tutorialpage" + (new Random()).nextInt(999999));
        Context childContext = newChildScope.createContext(context);
        View newChild = Layouts.createView(childContext, screen);

        container.addView(newChild);
        return newChild;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        View view = ((View) object);
        container.removeView(view);
        MortarScope.getScope(view.getContext()).destroy();
    }
}

The problem statement: it's crashing, as SubView class hasn't been added into list of Injections at the "Layouts.createView(childContext, screen);" moment in the Adapter, and I can't add it by default, because I want to have a @provider of data from SubScreen to SubScreen.Presenter. (I'm using local variable.

If I add SubView.class into list of injections and convert local Screen's variables into static, then I'll have 3 identical pages inside the ViewPager (which is logical, as every next call of the constructor - overrides old static variables).

Any help/ideas?

Thanks

ksloginov commented 9 years ago

Ok, apparently I did something.

First of all, adding SubView into list of globally injected classes. Then modifying SubScreen class:

@Layout(R.layout.screen_subscreen)
public class SubScreen extends Path {
    private static String titleStatic; // Introducing static variable
    private final String title;
    public SubScreen(String titleParam) {
        title = titleParam;
    }

    public void refreshPresenter() {
        titleStatic = title;
    }

    @Singleton
    public static class Presenter extends ViewPresenter<SubView> {

        private String title;

        @Inject
        public Presenter() {
        }

        @Override
        protected void onLoad(Bundle savedInstanceState) {
            super.onLoad(savedInstanceState);
            if (!hasView()) {
                return;
            }

            getView().setTitle(titleStatic);
        }
    }
}

and then in Custom adapter do this changes:

public class CustomPagerAdapter extends PagerAdapter {
    private final Context context;
    private final SubScreen[] screens;

    public CustomPagerAdapter(Context context, SubScreen[] screens) {
        this.context = context;
        this.screens = screens;
    }
    ......
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        SubScreen screen = screens[position];
        MortarScope originalScope = MortarScope.getScope(context);
        MortarScope newChildScope =  originalScope.buildChild().build("tutorialpage" + position);
        Context childContext = newChildScope.createContext(context);

        screen.refreshPresenter(); // updating the static var with local one!

        View newChild = Layouts.createView(childContext, screen);
        container.addView(newChild);
        return newChild;
    }
    ....
}

I.e. the idea is to keep the local AND static variables in the Screen, if the same screen is going to be reused. And when we inflate the view it - just setting the right value to the static one (that would be used in the Presenter).

It works, though I'm not sure if it's a right way to do it, so would be very happy, if someone can approve this approach or propose a better solution.

lukaspili commented 9 years ago

In my opinion, your logic of static variable and refreshPresenter() looks wrong. See here for an up to date example of view pager with Mortar and Flow: https://github.com/lukaspili/Mortar-Flow-Dagger2-demo In particular, look at the following classes: SlidesScreen, SlidePageScreen, SlidePagerAdapter.

And in the future, you should use stack overflow for such topic.

vsumtsov-gh commented 8 years ago

At this gist, you can find above solution but with save state functionality. https://gist.github.com/VladSumtsov/2128e0cc0f7c2131c0f971ee26d36ce5