Android Data Binding conditional menu? - android

I'm try to set different menus based on the user's role using the data binding library.
In the fragment of code below you can see that if the user is an educator I would set the activity_main_educator_drawer otherwise the activity_main_child_drawer.
<?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"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="sc.me.twelve.viewmodels.UserViewModel" />
</data>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_main"
app:menu="#{user.getModel.getRole.equals(`educator`) ? #menu/activity_main_educator_drawer : #menu/activity_main_child_drawer}" >
<include
layout="#layout/nav_header_main"
bind:user="#{user}" />
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
</layout>
It seems good, but I have this error:
****/ data binding error ****msg:Identifiers must have user defined types from the XML file. activity_main_educator_drawer is missing it
FINALLY
The George Mount's answer is the right one, I've just added the BindingAdapter.
#BindingAdapter("app:menu")
public static void setMenu(NavigationView navigationView, int id) {
navigationView.inflateMenu(id);
}

It looks like the menu resource isn't being recognized. Try this expression instead:
<android.support.design.widget.NavigationView
...
app:menu="#{user.getModel.getRole.equals(`educator`) ? R.menu.activity_main_educator_drawer : R.menu.activity_main_child_drawer}" >
Make sure to import the R class:
<data>
<import type="sc.me.twelve.R"/>
</data>
or whatever your package is.
I see a small strangeness in your expression. I think it can be simplified:
app:menu="#{user.model.role.equals(`educator`) ? R.menu.activity_main_educator_drawer : R.menu.activity_main_child_drawer}"
The "get" or "is" prefix is assumed for accessor methods.

Related

Navbar appear 2 times when use databinding

Hi I am trying use the data-bitindng with the navbar for do it I have the next code:
MapActivity.java
public class MapActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener, OnMapReadyCallback, GoogleMap.OnMarkerClickListener{
private static final float INITIAL_MAP_ZOOM_LEVEL = 17;
private static final int INITIAL_REQUEST_CODE = 1;
private GoogleMap mMap;
private CameraUpdate cameraUpdate = null;
private MapViewModel mapViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMapBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_map);
mapViewModel = ViewModelProviders.of(this).get(MapViewModel.class);
binding.setMapViewModel(mapViewModel);
binding.setLifecycleOwner(this);
NavHeaderMapBinding headerBinding = NavHeaderMapBinding.inflate(getLayoutInflater());
headerBinding.setMapViewModel(mapViewModel);
binding.navView.addHeaderView(headerBinding.getRoot());
setDrawerLayout();
setNavigationView();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setMap();
setButtonEvents();
}
...
map_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="mapViewModel"
type="com.emtmadrid.cardiomadapp.map.MapViewModel" />
</data>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/app_bar_map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_map"
app:menu="#menu/activity_map_drawer" />
</android.support.v4.widget.DrawerLayout>
</layout>
app_nav_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="mapViewModel"
type="com.emtmadrid.cardiomadapp.map.MapViewModel" />
</data>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".map.MapActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/white"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
</layout>
When execute this code, I get the screen of the MapActivity but with 2 navbar, the first navbar databinding not work, and in the second navbar work perfectly.
How can solve this?
Any idea?
Thanks.
To solve the problem I dropped the following line in the app_nav_bar.xml
app:headerLayout="#layout/nav_header_map"
I'd like to challenge the accepted answer, because removing app:headerLayout appears counter-productive to me ...the data-binding is just missing the bind attribute on the include nodes:
<include
layout="#layout/app_bar_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
bind:mapViewModel="#{mapViewModel}"/>
The same would go for this include, which should be moved outside of app_nav_bar.xml:
<include layout="#layout/content_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
bind:mapViewModel="#{mapViewModel}"/>
The result would be a working data-binding, where one only has to assign the MapViewModel once; the name-space for bind would be xmlns:bind="http://schemas.android.com/apk/res-auto".
When app_nav_bar.xml would only contain the AppBarLayout, it might be easier to handle; especially that include layout="#layout/content_map" appears quite confusing, in a file which is called app_nav_bar.xml. inflating the NavHeaderMapBinding is not required when having it already assigned with app:headerLayout - but the bind attribute is required, in order to have it bound.
Data-Binding Expressions: Includes also explains the combination of include & bind - which is generally less effort than inflating & binding several views by code - because inflating & binding the view once only works, when data-binding the include nodes properly with the layout resource XML. it's the actual beauty of data-binding, not having to bind all the includes by code.

Set ObservableArrayList<ModelClass> to RecyclerView using DataBinding

I want to update data items in the RecyclerView after API call. I have used ObservableArrayList. After api call, I have used notifyChange() in setter method. When I am passing the ObservableArrayList to the XML then I am getting error like this:
msg:Cannot find the setter for attribute 'bind:categories_list' with parameter type android.databinding.ObservableArrayList<T> on android.support.v7.widget.RecyclerView
XML is:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="list"
type="ObservableArrayList" />
<import type="android.databinding.ObservableArrayList" />
</data>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/white">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:categories_list="#{list}" />
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
</layout>
You need to use the #BindingAdapter("categories_list") annotation where you are handling the list. Check this answer for more details
EDIT
Not sure if you followed the right approach. The syntax looks weird. Usually here:
<variable
name="list"
type="ObservableArrayList" />
goes a model class of your own, and importing it and using it with both declarations also look weird to me, did you try:
<variable
name="list"
type="android.databinding.ObservableArrayList" />
?

Multiple root error in Layout- how to fix it?

Hello evreyone,
I get this error from my Layout XML: Multiple root error. So I tried a lot to fix it yet could not find any solution, please help me about it:
this is my code from file layout xml:
thanks a lot :)
You can have only one element in your XML that doesn't have a parent. By sparse information you gave us, you have at least two root elements: <RelativeLayout> and <android.support.v4.widget.DrawerLayout >.
To fix this error you just need to move </RelativeLayout> (closing tag) after DrawerLayout, that is to the end of the document. Or, probably better, move <android.support.v4.widget.DrawerLayout > to the beginning, enclosing other layouts.
However even if you do fix error you're getting this is not good implementation. You should read more about DrawerLayout.
you are getting Multiple root error in Layout because of
in Android every xml file there must be only one root layout
so try to add single parent to your layout
sample code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- add here your other controlls or layouts-->
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/app_bar_home_class"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_view_home"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_home_class"
app:itemBackground="#android:color/transparent"
app:itemIconTint="#color/drawer_item"
app:itemTextColor="#color/drawer_item"
app:menu="#menu/activity_home_drawer" />
</android.support.v4.widget.DrawerLayout>
</LinearLayout>

Breaking down complex XML layouts using the same data-binding context

Context
XML layouts in Android can get complicated. Hence, it is a good practice to break them down into conceptually independent modules. Consider the following example:
Main layout:
<layout>
<data>
<variable name="someVar" type="some.custom.Type"/>
</data>
<SomeLayout
...
android:someAttribute="#{someVar.someProperty}" />
<include layout="#layout/some_other_layout />
</layout>
and some_other_layout.xml:
<SomeOtherLayout
...
android:someOtherAttribute="#{someVar.someOtherProperty}" />
Problem
Is it possible to use the same data-binding context (whatever is inside <data>) in two separated layouts (like in the given example)?
Doing this naively results in java.lang.IllegalStateException.
From the Data Binding Library documentation:
Variables may be passed into an included layout's binding from the containing layout by using the application namespace and the variable name in an attribute:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/name"
bind:user="#{user}"/>
<include layout="#layout/contact"
bind:user="#{user}"/>
</LinearLayout>
</layout>

'cannot find symbol variable' in android data binding include layout

layout_content.xml
<layout>
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
/>
</android.support.design.widget.AppBarLayout>
</layout>
layout_main.xml
<layout>
<android.support.v4.widget.DrawerLayout
android:id="#+id/dl_main_drawer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="#layout/layout_content" android:id="#+id/content"/>
</android.support.v4.widget.DrawerLayout>
</layout>
MainActivity.java
LayoutMainBinding binding = DataBindingUtil.setContentView(this,R.layout.layout_main);
setSupportActionBar(binding.content.toolbar);
Android Studio intellisense check binding.content is ViewDataBinding obj
but build error 'cannot find symbol variable content'
Will this have any problem?
thx!
The layout activity_main.xml:
<layout>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/layout_content" android:id="#+id/content" />
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
</layout>
generates ActivityMainBinding.java. In your MainActivity.java, you use the generated field for content in the setSupportActionBar argument:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
setSupportActionBar(binding.content.toolbar);
}
Normally a layout will generate public final fields for each of the Views with android:ids and Binding subclasses for each of the includes with IDs. In this case, the data binding system did not detect that the included content #layout/layout_content was a binding layout and thus didn't capture the Binding class for the include.
When a variable is bound to an include, the data binding system will use that to determine that the included layout is a binding layout. So, if your layout had this instead:
<include layout="#layout/layout_content"
android:id="#+id/content"
app:someVar="#{someVar}" />
You'd have gotten a content field with the type LayoutContentBinding. This does assume that someVar is declared in both activity_main.xml and layout_content.xml.
The error in Android Studio was pointing to the correct location, but it was difficult to understand. In the future, you can look for the generated binding class in your app/build directory. This may help you figure out what the error means.
I've filed a bug to fix the error -- we should be generating a public final field for the include with the ID.

Categories

Resources