I am new to android and trying out the example of data binding as below. The text is not displayed, rather an empty box is displayed.
build.gradle :
databinding {
enabled = true
}
HelloWorld.java :
package com.example.android.databinding;
public class HelloWorld {
public String getText() {
return "My Name is ";
}
}
MainActivity.java :
package com.example.android.databinding;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import com.example.android.databinding.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
HelloWorld mViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setViewModel(mViewModel);
activityMainBinding.executePendingBindings();
}
}
activity_main.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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.android.databinding.HelloWorld" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.text}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Output :
You need to instantiate the HelloWorld class before sending it via DataBinding
mViewModel = new HelloWorld();
activityMainBinding.setViewModel(mViewModel);
Also, you don't have a filed text in HelloWorld class, so android:text="#{viewModel.text}" won't get compiled.
So you need to add it, and set its value, I am using constructor in that:
public class HelloWorld {
String text;
public HelloWorld() {
text = getText();
}
public String getText() {
return "My Name is ";
}
}
Related
I make my first step in android and want to use MVVM in my first project. But I faced with problem: when i try to call function from .xml file nothing happens. Below you can see important parts of project.
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.hfad.firebasechat.viewmodels.LoginViewModel"/>
<variable name="activity" type="com.hfad.firebasechat.views.MainActivity"/>
<import type="android.widget.Toast"/>
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:orientation="vertical">
<EditText
android:id="#+id/inEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"
android:padding="8dp"
android:text="#={viewModel.userEmail}" />
<EditText
android:id="#+id/inPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:padding="8dp"
android:text="#={viewModel.userPassword}" />
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{() -> activity.showToast()}"
android:layout_marginTop="8dp"
/>
</LinearLayout>
</ScrollView>
</layout>
MainActivity.java
package com.hfad.firebasechat.views;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.app.Dialog;
import android.content.Context;
import android.widget.Button;
import android.widget.Toast;
import android.os.Bundle;
import android.view.View;
import com.hfad.firebasechat.R;
import com.hfad.firebasechat.databinding.ActivityLoginBinding;
import com.hfad.firebasechat.viewmodels.LoginViewModel;
public class MainActivity extends AppCompatActivity {
private LoginViewModel mViewModel;
private Dialog mChatRoomDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ActivityLoginBinding activityLoginBinding= DataBindingUtil.setContentView(this, R.layout.activity_login);
mViewModel= new LoginViewModel();
activityLoginBinding.setViewModel(mViewModel);
activityLoginBinding.setLifecycleOwner(this);
}
public void showToast(){
Button btn = (Button) findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast toast = Toast.makeText(getApplicationContext(),
"Text here", Toast.LENGTH_LONG);
toast.show();
}
});
}
}
Thank yu for your answers.
Good luck.
P.S. if you have some advices for a baby in the world of android development i will glad to see it.
I spent some hours for this trying change fuctions which returns context of getApplicationContext() getContext() etc. It did not give me result. Button works correctly, but when I click it logcat does not show anything.
Also there was a try to write a fuction in xml #{() -> Toast(context, text, time).show()} but it also did not bring result. An unclever solution.
I have a strange problem regarding a BottomNavigationBar which I could not solve altough having spent a huge amount of time into it. When I use it in the 'recommended' way (from many tutorials) it just does not navigate.
So what do I mean by 'recommended' way: I have a single acticity with a navHostFragment called 'MainActivity'. This main activity has a XML layout file in which I put the BottomNavigationBar. The BottomNavigationBar also has a XML layout file. Now I have a Fragment called 'FR_Menu' with a Java file and a XML layout file. I also have a NavGraph. In the XML layout of the Fragment 'FR_Menu' I do not use the BottomNavigationBar and in the Java class I do not instantiate the BottomNavigationBar as I have already done this in the main Activity. But using this approach the Navigation does not work. Altough the BottomNavigationBar is correctly displayd in the Fragment 'FR_Menu', when clicking on the Bottom just nothing happens.
Now here come the strange thing. When I use the code as posted, but not put the BottomNavigationBar in the XML layout file of the main activity and instead put it separately in every XML-layout file of all Fragments (in this example the fragment 'FR_Menu') and when I also instantiate the BottomNavigationBar in every Java file of each Fragment (in this example the fragment 'FR_Menu'), then the Navigation works perfectly. So with this approach I have to put the BottomNavigationBar in every XML layout file of the fragments and also I have to instantiate the BottomNavigationBar in every Java file of the Fragments. I know that normally using the Jetpack Navigation components, this should not be the case and instead I should only have to instantiate once in the MainActivity as the BottomNavigationBar should only be added to the XML layout file of the MainActivity.
Does anyone have an idea what goes wrong when I am trying to implement the 'recommended' approach? The names are all correct (maybe there is a small error in my shown example because I had to simplify and adjust it a little bit), because when using the second approach (putting the BottomNavigationBar in every XML-layout file and in every Java file of the fragments) the navigation works perfectly (I also tried it with multiple items and destinations).
I have really spent quite much time on that and I could not figure out what my mistake it. Because of this I would highly appreciate every comment and would be quite thankful for your help.
Does anyone have an idea what the reason for this strange behaviour might be?
Java code of the 'MainActivity':
package com.example.td.bapp;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.td.bapp.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
binding = ActivityMainBinding.inflate(inflater, container, false);
NavController navController = Navigation.findNavController(this, R.id.navHostfragment);
NavigationUI.setupWithNavController(binding.bottomNavigation,navController );
setContentView(binding.getRoot());
return binding.getRoot();
}
}
XML layout file of the 'MainActivity':
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity"
tools:ignore="ExtraText">
<fragment
android:id="#+id/navHostfragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
app:labelVisibilityMode="labeled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorGreen"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_navigation"
app:itemIconTint="#color/colorPrimaryDark"
app:itemTextColor="#color/colorAccent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
XML-layout file of BottomNavigationBar:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/FR_LanguageSelection"
android:icon = "#drawable/ic_add_circle_full"
android:title = "Language" />
</menu>
Java file of the Fragment 'FR_Menu'
package com.example.td.bapp;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
import com.example.td.bapp.databinding.ActivityMainBinding;
import com.example.td.bapp.databinding.FragmentMenuBinding;
public class FR_Menu extends Fragment implements View.OnClickListener {
// TODO: Rename and change types of parameters
public FR_Menu() {
}
public static FR_Menu newInstance(String param1, String param2) {
FR_Menu fragment = new FR_Menu();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private FragmentMenuBinding binding;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// return inflater.inflate(R.layout.fragment_menu, container, false);
binding = FragmentMenuBinding.inflate(inflater, container, false);
/*
// IMPORTANT REMARK: When I use the following code in the second option this in the second option,
with the BottomNavigationBar in the XML document, the navigation works well
NavController navController = Navigation.findNavController(getActivity(), R.id.navHostfragment);
NavigationUI.setupWithNavController(binding.bottomNavigation,navController );
*/
return binding.getRoot();
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.imageButton_A.setOnClickListener(this);
binding.imageButton_B.setOnClickListener(this);
}
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
#Override
public void onClick(View view) {
if(view.getId() == R.id.imageButton_A) {
String argument = DataBaseEntries.A;
FR_MenuDirections.ActionFRMenuToFRGenericD action =
FR_MenuDirections.actionFRMenuToFRGenericD(argument);
Navigation.findNavController(view).navigate(action);
}
if(view.getId() == R.id.imageButton_B) {
String argument = DataBaseEntries.B;
FR_MenuDirections.ActionFRMenuToFRGenericD action =
FR_MenuDirections.actionFRMenuToFRGenericD(argument);
Navigation.findNavController(view).navigate(action);
}
}
}
Here is the XML layout file of the fragment 'FR_Menu'
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar_mainActivity"
android:layout_width="match_parent"
android:layout_height="135dp"
android:background="#435cb53f"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:titleTextColor="#android:color/holo_green_light">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:gravity="center"
android:layout_gravity="center"
android:textColor="#android:color/white"
android:textSize="24sp"
android:text="Menu" />
</androidx.appcompat.widget.Toolbar>
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar_mainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".MainActivity"
tools:ignore="ExtraText">
<ImageButton
android:id="#+id/imageButton_A"
android:layout_width="0dp"
android:layout_height="128dp"
android:background="#00000000"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/imageButton_B"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/menu_A" />
<ImageButton
android:id="#+id/imageButton_B"
android:layout_width="0dp"
android:layout_height="128dp"
android:layout_marginTop="12dp"
android:background="#00000000"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/imageButton_A"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/menu_B" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
XML code of the NavGraph
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/nav_graph"
app:startDestination="#id/FR_LanguageSelection">
<fragment
android:id="#+id/FR_Menu"
android:name="com.example.td.bapp.FR_Menu"
android:label="FR_Menu"
tools:layout="#layout/fragment_menu" >
<action
android:id="#+id/action_FR_Menu_to_FR_LanguageSelection"
app:destination="#id/FR_LanguageSelection" />
</fragment>
<fragment
android:id="#+id/FR_LanguageSelection"
android:name="com.example.td.bapp.FR_LanguageSelection"
android:label="FR_LanguageSelection" >
<action
android:id="#+id/action_FR_LanguageSelection_to_FR_Menu"
app:destination="#id/FR_Menu" />
</fragment>
</navigation>
onCreateView is a method of a Fragment, not a method on Activity, so your method in your MainActivity isn't ever called by the framework.
In fact, that code isn't what you'd use in an Activity at all as per the View Binding documentation.
Therefore your MainActivity should instead look like:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate, and then call setContentView() on the returned view root
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
NavController navController = Navigation.findNavController(this,
R.id.navHostfragment);
NavigationUI.setupWithNavController(binding.bottomNavigation, navController);
}
}
Why is the function setVm(user) error?
This is MainActivity.java:
package com.example.heriprastio.databindingandroid;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.heriprastio.databindingandroid.databinding.ActivityMainBinding;
import com.example.heriprastio.databindingandroid.model.User;
public class MainActivity extends AppCompatActivity {
private User user;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
user = new User();
user.setName("Heri");
user.setEmail("heriprastio#gmail.com");
binding.setVm(user);
}
}
This is my model class:
package com.example.heriprastio.databindingandroid.model;
public class User {
String name;
String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
And this is my "activity_main" file layout:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<data>
<variable
name="vm"
type="com.example.heriprastio.databindingandroid.MainActivity"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{vm.text}"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{vm.email}"/>
</LinearLayout>
</layout>
In the function setVm(user) cannot be applied, why is this? Can you explain, about this error?
And I have to solve this error or replace it with what?
You've declared your activity as variable type in the layout, but in the activity code you're passing an User instance as layout variable. To make it working, you have to make next changes:
In your layout file change
type="com.example.heriprastio.databindingandroid.MainActivity"
to
type="com.example.heriprastio.databindingandroid.model.User"
Moreover, I see that you're using "#{vm.text}" variable in the layout, even though you don't have such method or field in the User class. I suppose you wanted to use the user's name instead. So you must change it to "#{vm.name}"
I am working in MVVM design pattern.
I want to use dataBinding or RXJava in order to notify the View when the model has changed.
dataBinding can do it inside xml.
but i want to notify the Activity on a change in the model and do something more complicated.
let's assume i want my TextView to change color when text in not empty.
can you help me to do it via dataBinding or RXJava ?
here is my code:
xml
<?xml version="1.0" encoding="utf-8"?>
<data>
<variable
name="viewModel"
type="edi.com.mydatabindingsimple.MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="#={viewModel.txt}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={viewModel.txt}" />
</LinearLayout>
Activity
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import edi.com.mydatabindingsimple.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
MyViewModel viewModel = new MyViewModel();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setViewModel(viewModel);
}
}
viewModel
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class MyViewModel extends BaseObservable{
private String txt;
#Bindable
public String getTxt() {
return txt;
}
public void setTxt(String txt) {
this.txt = txt;
notifyPropertyChanged(edi.com.mydatabindingsimple.BR.txt);
}
}
What you can do is create a callback mechanism to trigger a method in your Activity. This will still notify the ViewModel first, but the effect will be the same.
You can create a new interface (or add it inside the ViewModel)
public interface ViewModelCallback{
void ActionCallback();
}
In your Activity:
public class MainActivity extends AppCompatActivity implements ViewModelCallback
In your ViewModel
private ArrayList<ViewModelCallback> callbacks = new ArrayList<>();
public void notifyCallbacks(){
for(ViewModelCallback c : callbacks){
if(c != null){
c.ActionCallback
}
}
}
public void addCallback(ViewModelCallback c){
callbacks.add(c);
}
Call notifyCallback after your NotifyPropertyChanged().
And don't forget to add your activity as a callback in onCreate
viewModel.addCallback(this)
You should also remove it in onDestroy.
I got this error when I try to biding RecyclerView
Error:(15, 22) Cannot find the setter for attribute 'app:items' with parameter type android.databinding.ObservableArrayList<com.toong.databindingdemo.recycler.UserViewModel on android.support.v7.widget.RecyclerView.
Here is my code:
<?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="usersViewModel"
type="com.toong.databindingdemo.recycler.UsersViewModel" />
</data>
<android.support.v7.widget.RecyclerView
android:id="#+id/activity_users_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="#{usersViewModel.users}"
/>
</layout>
But in UsersViewModel I already have a public users array
package com.toong.databindingdemo.recycler;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.ObservableArrayList;
public class UsersViewModel extends BaseObservable{
#Bindable
public ObservableArrayList<UserViewModel> users;
public UsersViewModel()
{
this.users = new ObservableArrayList<>();
}
}
I have clean and rebuild project but it still not working. How can I fix this error?
There is no property like app:items in RecyclerView. You need to create custom Binding method for that
#BindingAdapter("items")
public static void entries(RecyclerView recyclerView, String[] array) {
//write your code to set RecyclerView adapter.
}
change type of array to your required datatype.