Data Binding Error Trying to Pass ViewModel into Include Layout with Abstract Variable Type
Asked Answered
L

2

13

*EDIT: To answer my own question, I had to add EditorViewModel as an import to the parent abstract class in the outer layout, and cast the viewModel to the parent class using app:viewModel="@{((EditorViewModel)viewModel)}", that was it! I swear I don't recall doing this cast before though...*

My problem results because the included layout is defining a type which is the parent abstract class, and NOT the child concrete class, of the viewModel the outer layout is trying to share with the included layout.

I've confirmed that changing the included layout's type to the child concrete class' type fixes the issue, however, it should work with the abstract class type...

Here is how I define my viewModel variable, this is the concrete class in the type below:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

    <variable
        name="viewModel"
        type="com.ootpapps.gpeofflinedatacollection.viewmodels.EquipmentEditorViewModel" />
</data>
...

A few statements later I include the layout, attempting to share the viewModel from above:

<include
            layout="@layout/layout_spinner_location"
            app:viewModel="@{viewModel}" />

Within the included layout the variable viewModel is defined as follows:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">

<data>

    <variable
        name="viewModel"
        type="com.ootpapps.gpeofflinedatacollection.viewmodels.EditorViewModel" />
</data>

And of course here's the EquipmentEditorViewModel showing it extends the abstract parent class EditorViewModel (defined as the parent class type above in the included layout):

public class EquipmentEditorViewModel extends EditorViewModel<Equipment> {

The error I receive is:

****/ data binding error ****msg:Cannot find the setter for attribute 'app:viewModel' with parameter type com.ootpapps.gpeofflinedatacollection.viewmodels.EquipmentEditorViewModel on com.ootpapps.gpeofflinedatacollection.databinding.LayoutSpinnerLocationBinding. file:C:\Users\Ryan\AndroidstudioProjects\GPEOfflineDataCollection\app\src\main\res\layout\content_equipment_editor.xml loc:31:33 - 31:41 ****\ data binding error ****

As I mentioned above, changing the type in layout_spinner_location to "EquipmentEditorViewModel" fixes the error, HOWEVER, I need to use the abstract type in order to re-use this view as it doesn't always use an EquipmentEditorViewModel, sometimes it needs a "ToolEditorViewModel" or a "MeasurmentEditorViewModel" all of which extend EditorViewModel.

Thank you very much for your help. Maybe I will get lucky and George Mount will stop by.

Laundress answered 20/7, 2017 at 4:7 Comment(4)
This may be a bug. I'll investigate it.Rearrange
@GeorgeMount thank you kindly, I'll provide full code privately if you wish to look into it. I have invalidated all caches in AS and deleted both .gradle caches, no change in behavior.Laundress
I tried it with generics in the base class just like the description and wasn't able to reproduce the problem. A repro case would help. It really should work.Rearrange
Hmm came across another project today, where I had to do the same casting... I wonder if it's a problem with my environment. Is there anything I should look at on my side locally first?Laundress
S
11

Just for clarity if anyone comes across this.

Added 3 approaches to tackle the problem, #3 seems to be the winner

This seems to be an issue with the new gradle 3 and upward with the following error: Cannot find the setter for attribute 'app:viewModel'

Before

parent xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewModel"
            type="com.myapp.android.viewmodel.base.PageWebViewViewModel" />

    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:orientation="vertical">

        <WebView
            android:id="@+id/webview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="@{viewModel.contentVisibility}"
            app:baseUrl="@{null}"
            app:builtInZoomControlsEnabled="@{false}"
            app:cacheMode="@{viewModel.webViewCacheMode}"
            app:data="@{viewModel.webViewData}"
            app:domStorageEnabled="@{true}"
            app:encoding="@{`UTF-8`}"
            app:headers="@{viewModel.headers}"
            app:javaScriptEnabled="@{true}"
            app:loadUrl="@{viewModel.webViewUrl}"
            app:mimeType="@{`text/html; charset=utf-8`}"
            app:webChromeClient="@{viewModel.webChromeClient}"
            app:webInterface="@{viewModel.webInterface}"
            app:webInterfaceName="@{viewModel.webInterfaceName}"
            app:webViewClient="@{viewModel.webViewClient}" />

        <include
            layout="@layout/request_error_view"
            app:viewModel="@{viewModel}" />

        <include
            android:id="@+id/lyt_loading"
            layout="@layout/loading_layout"
            app:viewModel="@{viewModel}" />
    </FrameLayout>
</layout>

include/child xml (layout/request_error_view)

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <variable
            name="viewModel"
            type="com.myapp.android.viewmodel.base.BaseViewModel" />
    </data>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/send_request_error_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="@{viewModel.errorVisibility}">

        <TextView
            android:id="@+id/text_error_sending_request"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@string/error_sending_request"
            android:textColor="@color/window_primary_text"
            android:textSize="@dimen/text_size_large" />

        <Button
            android:id="@+id/button_try_again"
            style="@style/SuperbalistButton.Green.NoInset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/standard_content_margin"
            android:onClick="@{viewModel::onClickReload}"
            android:text="@string/try_again" />
    </LinearLayout>
</layout>

After #1 (2nd variable approach, does not seem to work)

parent xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewModel"
            type="com.myapp.android.viewmodel.base.PageWebViewViewModel" />

        <variable
            name="viewModelBase"
            type="com.myapp.android.viewmodel.base.BaseViewModel" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:orientation="vertical">

        <WebView
            android:id="@+id/webview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="@{viewModel.contentVisibility}"
            app:baseUrl="@{null}"
            app:builtInZoomControlsEnabled="@{false}"
            app:cacheMode="@{viewModel.webViewCacheMode}"
            app:data="@{viewModel.webViewData}"
            app:domStorageEnabled="@{true}"
            app:encoding="@{`UTF-8`}"
            app:headers="@{viewModel.headers}"
            app:javaScriptEnabled="@{true}"
            app:loadUrl="@{viewModel.webViewUrl}"
            app:mimeType="@{`text/html; charset=utf-8`}"
            app:webChromeClient="@{viewModel.webChromeClient}"
            app:webInterface="@{viewModel.webInterface}"
            app:webInterfaceName="@{viewModel.webInterfaceName}"
            app:webViewClient="@{viewModel.webViewClient}" />

        <include
            layout="@layout/request_error_view"
            app:viewModel="@{viewModelBase}" />

        <include
            android:id="@+id/lyt_loading"
            layout="@layout/loading_layout"
            app:viewModel="@{viewModelBase}" />
    </FrameLayout>
</layout>

After #2 (casting approach, works)

parent xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewModel"
            type="com.myapp.android.viewmodel.base.PageWebViewViewModel" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:orientation="vertical">

        <WebView
            android:id="@+id/webview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="@{viewModel.contentVisibility}"
            app:baseUrl="@{null}"
            app:builtInZoomControlsEnabled="@{false}"
            app:cacheMode="@{viewModel.webViewCacheMode}"
            app:data="@{viewModel.webViewData}"
            app:domStorageEnabled="@{true}"
            app:encoding="@{`UTF-8`}"
            app:headers="@{viewModel.headers}"
            app:javaScriptEnabled="@{true}"
            app:loadUrl="@{viewModel.webViewUrl}"
            app:mimeType="@{`text/html; charset=utf-8`}"
            app:webChromeClient="@{viewModel.webChromeClient}"
            app:webInterface="@{viewModel.webInterface}"
            app:webInterfaceName="@{viewModel.webInterfaceName}"
            app:webViewClient="@{viewModel.webViewClient}" />

        <include
            layout="@layout/request_error_view"
            app:viewModel="@{((com.myapp.android.viewmodel.base.BaseViewModel) viewModel)}" />

        <include
            android:id="@+id/lyt_loading"
            layout="@layout/loading_layout"
            app:viewModel="@{((com.myapp.android.viewmodel.base.BaseViewModel) viewModel)}" />
    </FrameLayout>
</layout>

After #3 (import and cast approach, works)

parent xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.myapp.android.viewmodel.base.BaseViewModel" />
        <variable
            name="viewModel"
            type="com.myapp.android.viewmodel.base.PageWebViewViewModel" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:orientation="vertical">

        <WebView
            android:id="@+id/webview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="@{viewModel.contentVisibility}"
            app:baseUrl="@{null}"
            app:builtInZoomControlsEnabled="@{false}"
            app:cacheMode="@{viewModel.webViewCacheMode}"
            app:data="@{viewModel.webViewData}"
            app:domStorageEnabled="@{true}"
            app:encoding="@{`UTF-8`}"
            app:headers="@{viewModel.headers}"
            app:javaScriptEnabled="@{true}"
            app:loadUrl="@{viewModel.webViewUrl}"
            app:mimeType="@{`text/html; charset=utf-8`}"
            app:webChromeClient="@{viewModel.webChromeClient}"
            app:webInterface="@{viewModel.webInterface}"
            app:webInterfaceName="@{viewModel.webInterfaceName}"
            app:webViewClient="@{viewModel.webViewClient}" />

        <include
            layout="@layout/request_error_view"
            app:viewModel="@{(BaseViewModel) viewModel)}" />

        <include
            android:id="@+id/lyt_loading"
            layout="@layout/loading_layout"
            app:viewModel="@{((BaseViewModel) viewModel)}" />
    </FrameLayout>
</layout>
Successor answered 18/12, 2017 at 12:13 Comment(0)
G
4

This method works perfectly

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
         <variable
            name="viewmodel"
           type="com.myapp.data.ViewModel" />
  </data>
    <include
        android:id="@+id/include"
        layout="@layout/buttons_layout" />

</layout>

buttons_layout

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
         <variable
            name="viewmodel"
           type="com.myapp.data.ViewModel" />
  </data>
     <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{() -> viewmodel.onClickChangeName()}"
        android:text="Change Name"
        app:layout_constraintTop_toBottomOf="@id/include"
        app:layout_constraintStart_toStartOf="parent"
        />
</layout>

MainActivity

lateinit var dataBinding: ActivityMainBinding
lateinit var viewModel: viewModel
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
dataBinding.include.viewModel = viewModel
dataBinding.viewModel = viewModel
dataBinding.executePendingBindings()
    
Garrulity answered 4/6, 2021 at 13:43 Comment(2)
Why do you need to set the viewmodel to both the parent as well as the included layout? It's weirdKostman
@RanaUmer thank you it's work for meSchooling

© 2022 - 2024 — McMap. All rights reserved.