Search code examples
androidandroid-fragmentsandroid-stylesandroid-library

Library's style attributes have no values even though they are explicitly set


I made a library with a custom view that inflates a layout when created. Views in the layout are styled with style="?attr/labelStyle" or any other attribute.

The attribute is declared the library's attrs.xml:

<attr name="myViewStyle" format="reference"/>

<declare-styleable name="MyView">
    <attr name="labelStyle" format="reference|color"/>
</declare-styleable>

I have set a default value to this attribute in the library's styles.xml:

<style name="MyViewStyle">
    <item name="labelStyle">@style/LabelStyle</item>
</style>

<style name="LabelStyle">
    <item name="android:textColor">?android:attr/textColorPrimary</item>
    <item name="...">...</item>
</style>

And finally in the library's themes.xml:

<style name="MyViewStyleLight" parent="Theme.AppCompat.Light">
    <item name="myViewStyle">@style/MyViewStyle</item>
</style>

Now this was the library's default styles, but it is overridden in the main project styles.xml

<style name="AppTheme" parent="Theme.AppCompat.Light">
    <item name="myViewStyle">@style/MyViewStyleCustom</item>
</style>

<style name="MyViewStyleCustom" parent="MyViewStyleLight">
    <item name="android:textColor">@color/gray</item>
    <item name="...">...</item>
</style>

The custom view code:

public MyView(Context context) {
    this(context, null, R.attr.myViewStyle, 0);
}

public MyView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, R.attr.myViewStyle, 0);
}

public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(createThemeWrapper(context, R.attr.myViewStyle, R.style.MyViewStyleLight),
            attrs, defStyleAttr, defStyleRes);
    initLayout();
}

private static Context createThemeWrapper(Context context, int styleAttr, int defaultStyle) {
    final TypedArray ta = context.obtainStyledAttributes(new int[]{styleAttr});
    int style = ta.getResourceId(0, defaultStyle);
    ta.recycle();
    return new ContextThemeWrapper(context, style);
}

private void initLayout() {
    LayoutInflater inflater = LayoutInflater.from(getContext());
    inflater.inflate(R.layout.my_view, this);
    ...
}

I explain about the ContextThemeWrapper below. Now the app crashes on the line where the layout gets inflated. Here's the important part of the crash log:

android.view.InflateException: Binary XML file line #0: Binary XML file line #0: Error inflating class com.example.MyView
      at android.view.LayoutInflater.inflate(LayoutInflater.java:539)
      at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
      [...]
    Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 13: TypedValue{t=0x2/d=0x7f030057 a=-1}
      at android.content.res.TypedArray.getDrawable(TypedArray.java:867)
      [...]

The layout inflater can't find the attribute's value. When I tried to get the attribute by code, it returns nothing. The attribute actually exists, only it has no value set to it even though I have clearly set one.

How exactly I am supposed to style my library? I am almost certain that I did every thing the same as the SublimePicker library but it just won't work. There's a little difference in the part with the ContextThemeWrapper, but it probably isn't the problem. I feel like I forgot a tiny thing somewhere that makes the attribute have no value, something is not connected, I don't know.

I know this is a very long question, but it cannot be more concise, I simplified everything as much as I could. I changed most of the information that was in the previous version of my question, making it completely different. The two answers are not relevant at all now, not that they ever were. The bounty was automatically rewarded.

If that could help someone I can add a download to my actual project, but as I said this simplified example has the exact same form as my project.


Solution

  • This answer is based on what I understand from your question and conversation between you and Vinayak B. If I misinterpret ,Please correct me.

    there is difference in style.xml in both place app as well as lib. addition I have removed theme.xml as well as changes in constructor of MyView.java for default style

    I have changed following things

    • overridden in the main project styles.xml

      <style name="AppTheme" parent="Theme.AppCompat.Light">
          <item name="myViewStyle">@style/MyViewStyleCustom</item>
      </style>
      
      <style name="MyViewStyleCustom" parent="MyViewStyle">
          <item name="labelStyle">@style/LabelStyle123</item>
      </style>
      
      <style name="LabelStyle123">
          <item name="android:textColor">#f00</item>
      </style>
      
    • lib styles.xml

      <resources>
          <style name="MyViewStyle">
              <item name="labelStyle">@style/LabelStyle</item>
              <item name="TextStyle">@style/textStyle</item>
          </style>
      
          <style name="LabelStyle">
              <item name="android:textColor">#00f</item>
          </style>
      
          <style name="textStyle">
              <item name="android:textColor">#009</item>
          </style>
      </resources>
      
    • MyView.java - changed constructor of and set default MyViewStyle if no any attribute come from application.

      public MyView(Context context) {
          this(context, null, R.attr.myViewStyle,  R.style.MyViewStyle);
      }
      
      public MyView(Context context, @Nullable AttributeSet attrs) {
          this(context, attrs, R.attr.myViewStyle,  R.style.MyViewStyle);
      }
      
      public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, R.style.MyViewStyle);
      }
      
      public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(createThemeWrapper(context, defStyleAttr,defStyleRes), attrs, defStyleAttr, defStyleRes);
          initLayout();
      }
      
      private static Context createThemeWrapper(Context context, int styleAttr, int defaultStyle) {
          final TypedArray ta = context.obtainStyledAttributes(new int[]{styleAttr});
          int style1 = ta.getResourceId(0, defaultStyle);
          ta.recycle();
          return new ContextThemeWrapper(context, style1);
      }
      

    so either it will take default labelStyle if it is not overridden in main activity style or overridden labelStyle