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

Inflation crash when using Mortar #135

Closed
nathanielwolf opened this Issue Feb 9, 2015 · 6 comments

Comments

Projects
None yet
2 participants
@nathanielwolf

nathanielwolf commented Feb 9, 2015

The following crash occurs on the first view inflation unless CalligraphyConfig.Builder#disableCustomViewInflation() is called during initialization.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.couchsurfing.mobile.debug/com.couchsurfing.mobile.ui.MainActivity}: android.view.InflateException: Binary XML file line #3: Error inflating class com.couchsurfing.mobile.ui.home.HomeView
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2184)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2233)
at android.app.ActivityThread.access$800(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: android.view.InflateException: Binary XML file line #3: Error inflating class com.couchsurfing.mobile.ui.home.HomeView
at android.view.LayoutInflater.createView(LayoutInflater.java:620)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.createCustomViewInternal(CalligraphyLayoutInflater.java:206)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.access$000(CalligraphyLayoutInflater.java:20)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater$PrivateWrapperFactory2.onCreateView(CalligraphyLayoutInflater.java:297)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:689)
at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.inflate(CalligraphyLayoutInflater.java:60)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.couchsurfing.mobile.ui.ScreenConductor.inflateLayout(ScreenConductor.java:226)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:221)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:207)
at com.couchsurfing.mobile.ui.ScreenConductor.createViewFromScreen(ScreenConductor.java:124)
at com.couchsurfing.mobile.ui.ScreenConductor.showScreen(ScreenConductor.java:75)
at com.couchsurfing.mobile.ui.base.BaseViewActivity.showScreen(BaseViewActivity.java:78)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.showScreen(BaseActivityPresenter.java:347)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.showScreen(MainActivityBlueprint.java:158)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.onLoad(BaseActivityPresenter.java:167)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.onLoad(MainActivityBlueprint.java:312)
at mortar.Presenter$1.onLoad(Presenter.java:34)
at mortar.RealActivityScope.doLoading(RealActivityScope.java:153)
at mortar.RealActivityScope.register(RealActivityScope.java:68)
at mortar.Presenter.takeView(Presenter.java:73)
at com.couchsurfing.mobile.ui.base.BaseActivity.onPostCreate(BaseActivity.java:182)
at com.couchsurfing.mobile.ui.MainActivity.onPostCreate(MainActivity.java:143)
at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1150)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2167)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2233)
at android.app.ActivityThread.access$800(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.constructNative(Constructor.java)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at android.view.LayoutInflater.createView(LayoutInflater.java:594)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.createCustomViewInternal(CalligraphyLayoutInflater.java:206)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.access$000(CalligraphyLayoutInflater.java:20)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater$PrivateWrapperFactory2.onCreateView(CalligraphyLayoutInflater.java:297)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:689)
at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.inflate(CalligraphyLayoutInflater.java:60)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.couchsurfing.mobile.ui.ScreenConductor.inflateLayout(ScreenConductor.java:226)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:221)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:207)
at com.couchsurfing.mobile.ui.ScreenConductor.createViewFromScreen(ScreenConductor.java:124)
at com.couchsurfing.mobile.ui.ScreenConductor.showScreen(ScreenConductor.java:75)
at com.couchsurfing.mobile.ui.base.BaseViewActivity.showScreen(BaseViewActivity.java:78)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.showScreen(BaseActivityPresenter.java:347)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.showScreen(MainActivityBlueprint.java:158)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.onLoad(BaseActivityPresenter.java:167)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.onLoad(MainActivityBlueprint.java:312)
at mortar.Presenter$1.onLoad(Presenter.java:34)
at mortar.RealActivityScope.doLoading(RealActivityScope.java:153)
at mortar.RealActivityScope.register(RealActivityScope.java:68)
at mortar.Presenter.takeView(Presenter.java:73)
at com.couchsurfing.mobile.ui.base.BaseActivity.onPostCreate(BaseActivity.java:182)
at com.couchsurfing.mobile.ui.MainActivity.onPostCreate(MainActivity.java:143)
at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1150)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2167)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2233)
at android.app.ActivityThread.access$800(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: java.lang.IllegalArgumentException: No inject registered for members/com.couchsurfing.mobile.ui.home.HomeView. You must explicitly add it to the 'injects' option in one of your modules.
at dagger.ObjectGraph$DaggerObjectGraph.getInjectableTypeBinding(ObjectGraph.java:302)
at dagger.ObjectGraph$DaggerObjectGraph.inject(ObjectGraph.java:279)
at mortar.Mortar.inject(Mortar.java:93)
at com.couchsurfing.mobile.ui.home.HomeView.(HomeView.java:25)
at java.lang.reflect.Constructor.constructNative(Constructor.java)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at android.view.LayoutInflater.createView(LayoutInflater.java:594)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.createCustomViewInternal(CalligraphyLayoutInflater.java:206)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.access$000(CalligraphyLayoutInflater.java:20)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater$PrivateWrapperFactory2.onCreateView(CalligraphyLayoutInflater.java:297)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:689)
at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
at uk.co.chrisjenx.calligraphy.CalligraphyLayoutInflater.inflate(CalligraphyLayoutInflater.java:60)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.couchsurfing.mobile.ui.ScreenConductor.inflateLayout(ScreenConductor.java:226)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:221)
at com.couchsurfing.mobile.ui.ScreenConductor.createView(ScreenConductor.java:207)
at com.couchsurfing.mobile.ui.ScreenConductor.createViewFromScreen(ScreenConductor.java:124)
at com.couchsurfing.mobile.ui.ScreenConductor.showScreen(ScreenConductor.java:75)
at com.couchsurfing.mobile.ui.base.BaseViewActivity.showScreen(BaseViewActivity.java:78)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.showScreen(BaseActivityPresenter.java:347)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.showScreen(MainActivityBlueprint.java:158)
at com.couchsurfing.mobile.ui.base.BaseActivityPresenter.onLoad(BaseActivityPresenter.java:167)
at com.couchsurfing.mobile.ui.MainActivityBlueprint$Presenter.onLoad(MainActivityBlueprint.java:312)
at mortar.Presenter$1.onLoad(Presenter.java:34)
at mortar.RealActivityScope.doLoading(RealActivityScope.java:153)
at mortar.RealActivityScope.register(RealActivityScope.java:68)
at mortar.Presenter.takeView(Presenter.java:73)
at com.couchsurfing.mobile.ui.base.BaseActivity.onPostCreate(BaseActivity.java:182)
at com.couchsurfing.mobile.ui.MainActivity.onPostCreate(MainActivity.java:143)
at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1150)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2167)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2233)
at android.app.ActivityThread.access$800(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(NativeStart.java)
@nathanielwolf

This comment has been minimized.

nathanielwolf commented Feb 25, 2015

Any help on this one?

@chrisjenx

This comment has been minimized.

Owner

chrisjenx commented Feb 25, 2015

I think someone else raised this? I use Mortar fine. I can only suggest this is a config issue :/ Without seeing how you included Mortar into your project, its hard to take a guess what's going on.

Caused by: java.lang.IllegalArgumentException: No inject registered for members/com.couchsurfing.mobile.ui.home.HomeView. You must explicitly add it to the 'injects' option in one of your modules.

That error suggestions its out of my hands, I would check that you are providing the correct scope/graph to your system service in your activity.

@nathanielwolf

This comment has been minimized.

nathanielwolf commented Feb 25, 2015

This error does not occur if I call CalligraphyConfig.Builder#disableCustomViewInflation() , but now all my custom views must have Calligraphy fonts set programatically. Does Calligraphy ever interfere with Dagger?

@chrisjenx

This comment has been minimized.

Owner

chrisjenx commented Mar 2, 2015

Calligraphy has no knowledge of Dagger. I use CustomView inflation with Dagger+Mortar+Flow.
All that I could assume is that the wrong context is being passed to the customViewInflation. Here
That mimics what the PhoneLayoutInflater actually does. What I might suggest is the context passed to your ScreenConductor might be slightly different and therefore your ScopedContexts are not being passed through the same (they shouldn't but I don't want to rule it out.) If we can work out what it is then I can put a more flexible inflation mechanism in to make it less "picky" in the future.

I inflate screens the following way.

public void showScreen(S screen, Flow.Direction direction, final Backstack newBackstack, final Flow.Callback callback) {
        MortarScope activityScope = Mortar.getScope(context);
        MortarScope newChildScope = activityScope.requireChild(screen);

        View oldChild = getActiveView();
        View newChild;


        Timber.d("Show Blueprint: %s", newChildScope.getName());
        if (oldChild != null) {
            MortarScope oldChildScope = Mortar.getScope(oldChild.getContext());
            Timber.d("Hide Blueprint: %s", oldChildScope.getName());
            if (oldChildScope.getName().equals(screen.getMortarScopeName())) {
                // If it's already showing, short circuit.
                return;
            }
            // Destroy the old scope, this gets a bit racy
            activityScope.destroyChild(oldChildScope);
        }

        // Create the new child.
        Context childContext = newChildScope.createContext(context);
        newChild = Layouts.createView(childContext, screen);

        displayView(oldChild, newChild, direction, callback);
    }

this.context is the activity context.

@chrisjenx

This comment has been minimized.

Owner

chrisjenx commented Mar 2, 2015

And for full disclosure this is how my FlowActivity works:

/**
 * Created by chris on 06/01/2014
 */
public abstract class FlowActivity extends ActionBarActivity {

    /**
     * Mortar Scope
     */
    private MortarActivityScope mActivityScope;
    private String mActivityScopeName;

    /**
     * Current flow stack
     */
    protected Flow mActivityFlow;

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

        if (isWrongInstance()) {
            finish();
            return;
        }

        // Action Bar
        supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        //--

        // Gets Parent Scope From Application via SystemService
        MortarScope parentScope = Mortar.getScope(getApplication());
        mActivityScope = Mortar.requireActivityScope(parentScope, getActivityBlueprint(getActivityScopeName()));
        Mortar.inject(this, this);

        mActivityScope.onCreate(savedInstanceState);
        setContentView(getActivityBlueprintContentRes());
        ButterKnife.inject(this);

        // Find the Container
        final View view = ButterKnife.findById(this, R.id.container);
        if (!(view instanceof HasFlow))
            throw new RuntimeException("The Activities Blueprint Screen, must implement FlowView");
        final HasFlow hasFlow = (HasFlow) view;
        mActivityFlow = hasFlow.getFlow();
    }

    @Override
    protected void onSaveInstanceState(@NonNull final Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mActivityScope != null && !mActivityScope.isDestroyed())
            mActivityScope.onSaveInstanceState(outState);
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        if (mActivityScope != null)
            return mActivityScope.getName();
        return super.onRetainCustomNonConfigurationInstance();
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mActivityResultHelper.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!isChangingConfigurations() && mActivityScope != null) {
            MortarScope parentScope = Mortar.getScope(getApplication());
            parentScope.destroyChild(mActivityScope);
            mActivityScope = null;
        }
        mActivityResultHelper = null;
    }

    /**
     * Inform the view about back events.
     */
    @Override
    public void onBackPressed() {
        // Give the view a chance to handle going back. If it declines the honor, let super do its thing.
        if (!mActivityFlow.goBack()) super.onBackPressed();
    }

    @Override
    protected void attachBaseContext(@NonNull Context newBase) {
        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
    }

    /**
     * Return a Unique Scope name per instance of this Activity, should reduce overlapping activity
     * Scope Name Conflict Issues!
     */
    protected String getActivityScopeName() {
        if (mActivityScopeName == null)
            mActivityScopeName = (String) getLastCustomNonConfigurationInstance();
        if (mActivityScopeName == null) {
            mActivityScopeName = getClass().getName() + "-" + UUID.randomUUID().toString();
        }
        return mActivityScopeName;
    }

    /**
     * <b>MUST<b> contain {@link com.justyoyo.ui.mortar.utils.HasFlow} with ID
     * {@link R.id#container}
     */
    protected abstract int getActivityBlueprintContentRes();

    /**
     * Return the Blueprint for this Activity which supports the subviews.
     * This contains the flow stuff.
     * <p/>
     * Inherit you concrete {@link mortar.Blueprint} from
     * {@link com.justyoyo.ui.mortar.core.Main}
     *
     * @param activityScopeName
     */
    protected abstract Blueprint getActivityBlueprint(final String activityScopeName);

    @Override
    public Object getSystemService(@NonNull String name) {
        if (Mortar.isScopeSystemService(name)) {
            return mActivityScope;
        }
        return super.getSystemService(name);
    }

    /**
     * Dev tools and the play store (and others?) launch with a different intent, and so
     * lead to a redundant instance of this activity being spawned. <a
     * href="http://stackoverflow.com/questions/17702202/find-out-whether-the-current-activity-will-be-task-root-eventually-after-pendin"
     * >Details</a>.
     */
    private boolean isWrongInstance() {
        if (!isTaskRoot()) {
            Intent intent = getIntent();
            boolean isMainAction = intent.getAction() != null && intent.getAction().equals(ACTION_MAIN);
            return intent.hasCategory(CATEGORY_LAUNCHER) && isMainAction;
        }
        return false;
    }
}

Hopefully that will help.

@nathanielwolf

This comment has been minimized.

nathanielwolf commented Mar 5, 2015

I have discovered the issue. Thank you for all this nice code to reference.

We have almost identical implementation, but with one difference. Instead of using Layout.createView(), we copied the code from Layout.createView() and modified it from:

  /** Create an instance of the view specified in a {@link Layout} annotation. */
  public static android.view.View createView(Context context, Object screen) {
    return createView(context, screen.getClass());
  }

  /** Create an instance of the view specified in a {@link Layout} annotation. */
  public static android.view.View createView(Context context, Class<?> screenType) {
    Layout screen = screenType.getAnnotation(Layout.class);
    if (screen == null) {
      throw new IllegalArgumentException(
          String.format("@%s annotation not found on class %s", Layout.class.getSimpleName(),
              screenType.getName()));
    }

    int layout = screen.value();
    return inflateLayout(context, layout);
  }

  private static android.view.View inflateLayout(Context context, int layoutId) {
    return LayoutInflater.from(context).inflate(layoutId, null);
  }

  private Layouts() {
  }

To:

  /** Create an instance of the view specified in a {@link flow.Layout} annotation. */
  private static android.view.View createView(Context context, Object screen, ViewGroup container) {
    return createView(context, screen.getClass(), container);
  }

  /** Create an instance of the view specified in a {@link flow.Layout} annotation. */
  private static android.view.View createView(Context context, Class<?> screenType,
      ViewGroup container) {
    Layout screen = screenType.getAnnotation(Layout.class);
    if (screen == null) {
      throw new IllegalArgumentException(
          String.format("@%s annotation not found on class %s", Layout.class.getSimpleName(),
              screenType.getName()));
    }

    int layout = screen.value();
    return inflateLayout(context, layout, container);
  }

  private static android.view.View inflateLayout(Context context, int layoutId,
      ViewGroup container) {
    return LayoutInflater.from(context).inflate(layoutId, container, false);
  }

We did this so layout margins applied to the child views worked correctly. I can find a work around for this, but this is a bug in Calligraphy. It seems when builder.disableCustomViewInflation(); is not called, the inflated view get's it's context from it's root, and from the LayoutInflater if the root is null. Calling builder.disableCustomViewInflation(); seems to force the inflated view to always get the context from the inflater - which seems like the way things should always work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment