ObservableField<Marker> value inside ViewModel class value is changed using EditText in layout however value is not propagated to TextView tv_summary.
This is the 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">
<data>
<import type="com.example.tutorial5livedata_mvvm_room_recyclerview.util.BindingUtils"/>
<variable
name="viewModel"
type="com.example.tutorial5livedata_mvvm_room_recyclerview.viewmodel.AddMarkerViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Title"
android:textColor="#FC7100"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/et_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="Title"
android:inputType="textPersonName"
android:text="#={viewModel.markerObservableField.title}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/tv_title" />
<TextView
android:id="#+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Latitude"
android:textColor="#FC7100"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/et_title" />
<EditText
android:id="#+id/et_latitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="Latitude"
android:text="#={viewModel.markerObservableField.latitude}"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/tv_latitude" />
<TextView
android:id="#+id/tv_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text='#{viewModel.markerObservableField.title + " " + viewModel.markerObservableField.latitude}'
app:layout_constraintStart_toStartOf="#+id/guideline_left"
app:layout_constraintTop_toBottomOf="#+id/et_latitude" />
<android.support.constraint.Guideline
android:id="#+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="8dp" />
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel class
public class AddMarkerViewModel extends AndroidViewModel {
private MarkerRepository mMarkerRepository;
public ObservableField<Marker> markerObservableField = new ObservableField<>();
public AddMarkerViewModel(#NonNull Application application) {
super(application);
AppDatabase appDatabase = AppDatabase.getInstance(application.getApplicationContext());
mMarkerRepository = MarkerRepository
.getsInstance(MarkerLocalDataSource.getInstance(appDatabase.markerDao(), new AppExecutors()));
if (markerObservableField.get() == null) {
Marker marker = new Marker();
marker.setTitle("New Title");
markerObservableField.set(marker);
}
}
public long addMarker(Marker marker) {
return mMarkerRepository.addMarker(marker);
}
}
onCreate method of Activity for adding marker to set values
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_add_marker);
mAddMarkerViewModel = ViewModelProviders.of(this).get(AddMarkerViewModel.class);
mBinding.setViewModel(mAddMarkerViewModel);
}
In order to listen to property changes, you will need to extend BaseObservable.
I think the problem here is that property change does not fire event, because you listen to Field change, that is marker object itself, that stays same.
Marker field Latitude is not observable, that means it's impossible to detect it's change.
You have two options.
If you want to detect changes, you can create observable field for Latitude.
public ObservableField<String> latitudeObservableField = new ObservableField<>();
You can listen to field Changes and update marker object.
latitudeObservableField.addOnPropertyChangedCallback(() -> {
// Update marker object
})
Another approach would be to make Marker extend BaseObservable, like explained in attached reference.
Please check out official documentation on observable objects.
Related
I already found the question here , but it didn't really work for me. I have a currency converter with two edit texts which convert from INR to USD and vise versa. I need to use DataBinding to listen to the changes of each Edittext, and update the currency value of the next EdittText
Hers my code
public class BindingViewModel extends BaseObservable {
public ObservableField<Double> inr = new ObservableField<>();
public ObservableField<Double> dollar = new ObservableField<>();
public void onINRTextChanged(CharSequence s, int start, int before, int count) {
double aDouble = Double.parseDouble(s.toString());
dollar.set(aDouble * 0.014);
}
public void onDOLLARTextChanged(CharSequence s, int start, int before, int count) {
Log.d("tag", "onTextChanged " + s);
double aDouble = Double.parseDouble(s.toString());
inr.set(71.92 * aDouble);
}
}
And XML
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="vm"
type="android.com.tasks.BindinfViewModel" />
</data>
<android.support.constraint.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">
<EditText
android:id="#+id/inr"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:ems="10"
android:inputType="number"
android:onTextChanged="#{vm.onDOLLARTextChanged}"
android:text="#{String.valueOf(vm.inr)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.17"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/dollar"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:ems="10"
android:inputType="number"
android:onTextChanged="#{vm.onINRTextChanged}"
android:text="#{String.valueOf(vm.dollar)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/inr"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="INR"
app:layout_constraintEnd_toEndOf="#+id/inr"
app:layout_constraintStart_toStartOf="#+id/inr"
app:layout_constraintTop_toBottomOf="#+id/inr" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="DOLLAR"
app:layout_constraintEnd_toEndOf="#+id/dollar"
app:layout_constraintStart_toStartOf="#+id/dollar"
app:layout_constraintTop_toBottomOf="#+id/dollar" />
</android.support.constraint.ConstraintLayout>
</layout>
Here, whenever I enter something in any of the EditTexts, both are going to an infinite loop since both text change listeners continuously triggering on each value update. I can solve this issue by manually adding a TextWatcher to each of the EditTexts and temporarily making textChangelistener as null just before setting the value to avoid infinite looping. Is there any other efficient way using databinding to do this?
I suggest you use two-way binding. you can write setter and getter for your fields like below and then use android:text="#={fieldName}" instead of android:text="#{fieldName}".
your Model class will maybe like this:
public class BindingViewModel extends BaseObservable {
public Double inr = 0.0;
public Double dollar = 0.0;
#Bindable
public String getInrString() {
String inrString = String.valueOf(inr);
if (inrString.endsWith(".0"))
return inrString.substring(0, inrString.length() - 2);
return inrString;
}
public void setInrString(String inrString) {
if (inrString != null && !inrString.isEmpty()) {
double newInr = Double.parseDouble(inrString);
if (this.inr != newInr) {
this.inr = newInr;
dollar = inr * 0.014;
}
} else {
inr = 0.0;
dollar = 0.0;
}
notifyPropertyChanged(BR.dollarString);
}
#Bindable
public String getDollarString() {
String dollarString = String.valueOf(dollar);
if (dollarString.endsWith(".0"))
return dollarString.substring(0, dollarString.length() - 2);
return dollarString;
}
public void setDollarString(String dollarString) {
if (dollarString != null && !dollarString.isEmpty()) {
double newDollar = Double.parseDouble(dollarString);
if (this.dollar != newDollar) {
this.dollar = newDollar;
inr = dollar * 71.92;
}
} else {
inr = 0.0;
dollar = 0.0;
}
notifyPropertyChanged(BR.inrString);
}
}
and you Layout XML:
<layout>
<data>
<variable
name="vm"
type="com.BindingViewModel" />
</data>
<android.support.constraint.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">
<EditText
android:id="#+id/inr"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:ems="10"
android:inputType="number"
android:text="#={vm.inrString}"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.17"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/dollar"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:ems="10"
android:inputType="number"
android:text="#={vm.dollarString}"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/inr"
app:layout_constraintTop_toTopOf="parent" />
...
and ofcourse if your activity you need to set your viewModel:
BindingViewModel viewModel=new BindingViewModel();
binding.setVm(viewModel);
How can I do databinding with livedata?
activity_user_detail.xml:
<data>
<variable
name="viewModel"
type="com.test.viewmodel.UserViewModel" />
</data>
<TextView
android:id="#+id/tv_amount"
android:layout_width="match_parent"
android:text="#{viewModel.age}"
....
UserViewModel.java:
public class UserViewModel extends ViewModel {
public LiveData<User> user;
public void getUserById(UserDao userDao, String userId){
transaction = UserDao .load(userId);
}
}
UserDao.java:
#Query("SELECT * FROM `user` WHERE id = :userId")
LiveData<User> load(String userId);
UserDetailActivity.java:
private ActivityUserDetailBinding binding;
binding = DataBindingUtil.setContentView(this, R.layout.activity_user_detail);
viewModel = ViewModelProviders.of(this).get(UserViewModel.class);
viewModel.getUserById(userDao, userId);
viewModel.user.observe(this, user -> binding.setViewModel(user)); // How to bind livedata?
I have tried this also:
binding.setViewModel(viewModel);
This is a sample to figure out how LiveData and ObservableField works. You need to change T object and setValue() with LiveData, or set(T) with ObservableField. Changing properties of object T does not update UI.
<?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.example.tutorial3livedata_databinding2objects.UserViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/user_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='#{viewModel.userMutableLiveData.name+ ", email " + viewModel.userMutableLiveData.email}'
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.1" />
<EditText
android:id="#+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="48dp"
android:text="#={viewModel.userMutableLiveData.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/user_info" />
<EditText
android:id="#+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="28dp"
android:text="#={viewModel.userMutableLiveData.email}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_name" />
<Button
android:id="#+id/button_change_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:onClick="#{() -> viewModel.changeUserName()}"
android:text="Change Name"
app:layout_constraintEnd_toStartOf="#+id/button_change_user"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_email" />
<Button
android:id="#+id/button_change_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{() -> viewModel.changeUser()}"
android:text="Change User"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/button_change_name"
app:layout_constraintTop_toTopOf="#+id/button_change_name" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="56dp"
android:text="Display User"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/button_change_name" />
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel
public class UserViewModel extends ViewModel {
public MutableLiveData<User> userMutableLiveData = new MutableLiveData<>();
private User mUser;
public UserViewModel() {
mUser = new User("User", "asd#abc.com");
userMutableLiveData.setValue(mUser);
}
public void changeUserName() {
// Both updates LiveData but does not update UI
mUser.setName("Name is Changed");
// userMutableLiveData.getValue().setName("Updated Name");
// This one Updates UI
// userMutableLiveData.setValue(userMutableLiveData.getValue());
}
public void changeUser() {
mUser = new User("New User Name", "myemail#mail.com");
// Without setting new value UI is not updated and observe is not called
userMutableLiveData.setValue(mUser);
}
}
MainActivity
/*
Without binding.setLifecycleOwner(this), liveData.setValue() does not update UI
liveData.setValue() updates UI
EditText changes LiveData but does not update UI
*/
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserViewModel userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
// LiveData should call setValue() to update UI via binding
binding.setViewModel(userViewModel);
// Required to update UI with LiveData
binding.setLifecycleOwner(this);
}
}
This code is for learning purposes.
Results you can get from this code:
1- Changing user name with changeUserName() updates the name of existing User of LiveData but does not update UI. UI gets updated when you rotate the device.
2- When you change User of LiveData and setValue() data-binding works.
3- Changing User properties using EditText 2-way binding android:text="#={viewModel.userMutableLiveData.name}" changes LiveData's User's name but does not update UI until device is rotated since User is still same.
I am learning mvvm structure and making an app using mvvm structure and data binding also.
Now, what I want to do is, I wanted to fetch a user from sharedpreference, If I am getting a user successfully then, I would set the name of usr to edittext1. In that case, I want to request focus on edittext2.
How to achieve that using databinding? (in such a way that i don't
have to use activity. The job should be done using view model and xml
only.)
I have tried that using following way.
StartGameViewModel
public class StartGameViewModel extends ViewModel {
public static String TAG="StartGameViewModel";
private Preference<User> preference;
public ObservableField<String> player1Name=new ObservableField<>("");
public ObservableField<String> player2Name=new ObservableField<>("");
public ObservableBoolean shouldRequestFocus=new ObservableBoolean(false);
StartGameViewModel(Preference preference){
this.preference=preference;
}
public void getPreference() {
preference.get(Constants.CURRENT_USER,User.class)
.subscribe(new Observer<User>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(User user) {
player1Name.set(user.name);
shouldRequestFocus.set(true);
}
#Override
public void onError(Throwable e) {
Log.i(TAG,"user is logged out");
}
#Override
public void onComplete() {
Log.i(TAG,"Completed Preference");
}
});
}
}
activity_start_game.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"
tools:context=".view.StartGameActivity">
<data>
<variable
name="startGameViewModel"
type="com.sevenbits.android.mvvmsample.viewmodel.StartGameViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/splash_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter Players Name"
android:textSize="24sp"
android:textColor="#color/white"
app:layout_constraintVertical_bias="0.75"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="#+id/input_layout"
app:layout_constraintTop_toTopOf="parent"
/>
<LinearLayout
android:id="#+id/input_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:background="#color/white"
android:elevation="20dp"
android:orientation="vertical"
android:paddingBottom="40dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.45"
tools:layout_editor_absoluteX="8dp">
<android.support.design.widget.TextInputLayout
android:id="#+id/til_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<android.support.design.widget.TextInputEditText
android:id="#+id/et_player1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Player1"
android:text="#{startGameViewModel.player1Name}"
app:addTextChangedListener="#{startGameViewModel.Player1Watcher}" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="#+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<android.support.design.widget.TextInputEditText
android:id="#+id/et_player2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Player2"
android:text="#{startGameViewModel.player2Name}"
app:addTextChangedListener="#{startGameViewModel.Player2Watcher}" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<Button
android:id="#+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/bg_button"
android:elevation="20dp"
android:gravity="center"
android:paddingBottom="20dp"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:paddingTop="20dp"
android:text="Start"
android:textAllCaps="false"
android:textColor="#color/white"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/input_layout" />
</android.support.constraint.ConstraintLayout>
</layout>
StartGameActivity:
public class StartGameActivity extends AppCompatActivity {
ActivityStartGameBinding activityStartGameBinding;
StartGameViewModel startGameViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initDataBinding();
setStartGameListener();
}
private void initDataBinding() {
activityStartGameBinding = DataBindingUtil.setContentView(this, R.layout.activity_start_game);
StartGameViewModelFactory startGameViewModelFactory= Injection.provideStartGameViewModelFactory(this);
startGameViewModel = ViewModelProviders.of(this,startGameViewModelFactory).get(StartGameViewModel.class);
activityStartGameBinding.setStartGameViewModel(startGameViewModel);
startGameViewModel.getPreference();
if(startGameViewModel.shouldRequestFocus.get())
findViewById(R.id.et_player2).requestFocus();
}
private void setStartGameListener() {
findViewById(R.id.start_button).setOnClickListener(v -> {
Intent intent=new Intent(StartGameActivity.this,GameActivity.class);
intent.putExtra(Constants.PLAYER1_NAME,startGameViewModel.player1Name.get());
intent.putExtra(Constants.PLAYER2_NAME,startGameViewModel.player2Name.get());
startActivity(intent);
});
}
}
Very simple via binding adapter
#JvmStatic
#BindingAdapter("requestFocus")
fun requestFocus(view: TextView, requestFocus: Boolean) {
if (requestFocus) {
view.isFocusableInTouchMode = true
view.requestFocus()
}
}
<EditText
android:id="#+id/et_company"
android:layout_width="0dp"
android:layout_height="match_parent"
android:imeOptions="actionDone"
android:inputType="textCapSentences"
android:maxLength="32"
android:scrollbars="vertical"
app:requestFocus="#{company.requestFocus}" />
I know this is an old question, but I was not able to solve the issue with the current answer. Posting my answer here:
in XML:
<EditText
android:id="#+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="#dimen/elevation15"
app:requestFocus="#{viewmodel.focus}"/>
And in the viewModel, I had a Boolean variable declared as
val focus = true
And the binding adapter is as follows:
companion object{
#BindingAdapter("requestFocus")
#JvmStatic fun requestFocus(editText: EditText, requestFocus: Boolean){
if(requestFocus){
editText.isFocusableInTouchMode = true
editText.requestFocus()
}
}
}
Basically what is happening here is, Since app:requestFocus is written inside the xml, it will invoke the binding adapter with the focus Boolean, which is given as the value for the attribute for app:requestFocus.
In my case, I didn't want to remove focus, that's why I used Boolean. If it needs to be changed, using a livedata and set the value, and update the binding adapter accordingly
Disclaimer: I am new to MVVM and Databinding, someone please correct me if I am wrong.
I am learning data binding library and I am totally new to this. My question is how to navigate to another activity on data binding.
Can I do it just like we do it in MVC or I have to put that code in ViewModel. Please help me.
Here is my code.
Activity:
public class SplashActivity extends AppCompatActivity implements Observer {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initDataBinding();
initViews();
}
private void initViews() {
findViewById(R.id.guest_button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(SplashActivity.this,GameActivity.class));
}
});
}
private void initDataBinding() {
ActivitySplashBinding activitySplashBinding = DataBindingUtil.setContentView(this, R.layout.activity_splash);
SplashViewModel splashViewModel = new SplashViewModel();
activitySplashBinding.setSplashViewModel(splashViewModel);
splashViewModel.addObserver(this);
}
#Override
public void update(Observable observable, Object data) {
}
}
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"
tools:context="com.sevenbits.android.mvvmsample.view.SplashActivity">
<data>
<variable name="splashViewModel"
type="com.sevenbits.android.mvvmsample.viewmodel.SplashViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:id="#+id/parent1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/splash_bg">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:gravity="center"
android:text="Login"
android:textColor="#color/white"
android:textSize="24sp"
android:id="#+id/login_button"
app:layout_constraintBottom_toTopOf="#+id/parent1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="#id/parent1"
app:layout_constraintVertical_bias="0.42"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:gravity="center"
android:text="Sign Up"
android:textColor="#color/white"
android:textSize="24sp"
android:id="#+id/sign_up_button"
app:layout_constraintBottom_toTopOf="#+id/parent1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="#id/parent1"
app:layout_constraintVertical_bias="0.58"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:textColor="#color/white"
android:textAllCaps="false"
android:textSize="18sp"
android:id="#+id/guest_button"
android:layout_marginBottom="20dp"
android:text="Play As a Guest User"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
</layout>
Do I need to put onclick code in view model? If yes, then How to do it?
Well, that really depends on the use case but ideally every action should go through ViewModel.
If you need to do some stuff before navigation like storing the data, your click action should go from ViewModel. If you just need to redirect, you can do it MVC way.
I generally do it in this way:
SplashViewModel.java
public static final int ACTION_NAVIGATE_TO_GAME = 1000;
public void navigateToGame() {
// You can do some work here before notifying the view about redirection
setChanged();
notifyObserver(ACTION_NAVIGATE_TO_GAME);
}
SplashActivity.java
#Override
public void update(Observable observable, Object data) {
if (data instanceof Integer) {
int value = (int) data;
switch(value) {
case SplashViewModel.ACTION_NAVIGATE_TO_GAME:
startActivity(new Intent(SplashActivity.this,GameActivity.class));
break;
}
}
}
and in layout file
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:textColor="#color/white"
android:textAllCaps="false"
android:textSize="18sp"
android:id="#+id/guest_button"
android:layout_marginBottom="20dp"
android:text="Play As a Guest User"
android:onClick="#{() -> splashViewModel.navigateToGame()}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
I’m using LiveData by Google now and they recomend to use a MVVM patter design. For some of my requests I use RxJava2, and listen for responses in SubscribeWith(…).
For example, when I press a button to send some data to the remote data source, I’m showing some loading animation and want to hide it onComplete() event (inside subscribeWith(…)). The problem is that I don’t have an access to the View from ModelView. How it’s possible to let the View know that loading animation should be hidden?
My current idea is to create in interface inside ViewModel and implement it in View. But it ruins the concept of View and ViewModel separation.
Well you can use liveData for this :D
At your ViewModel class you can create a live data object like this
MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
and for example make a function called downloadFinished and call it in the onComplete
for your remote code
private void downloadFinished() {
isLoading.setValue(true);
}
At your activity that use the view model you observe the value of the loading and hide the progress or what ever you want
TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
viewModel.isLoading.observe(this, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean isLoading) {
if (isLoading != null) {
if (isLoading) {
// hide your progress bar
}
}
}
});
you can use DataBinding for that too
make a separate layout to reuse it everywhere
laoding_state_xml
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="#+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
then include it inside your desired 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">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.blogspot.soyamr.notforgotagain.view.signin.SignInViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.signin.SignInFragment">
<include
android:id="#+id/include"
layout="#layout/toolbar_application"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/signInButtonView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="68dp"
android:layout_marginEnd="16dp"
android:onClick="#{() -> viewModel.logIn()}"
android:text="#string/sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/passwordTextInputLayout" />
<TextView
android:id="#+id/noAccountTextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="4dp"
android:text="#string/no_account"
android:textColor="#android:color/black"
app:layout_constraintEnd_toStartOf="#+id/createAccountTextView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/signInButtonView" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/emailTextInputLayout"
style="#style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="80dp"
android:layout_marginEnd="16dp"
app:errorEnabled="true"
app:errorText="#{viewModel.emailErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/include">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/email"
android:inputType="textEmailAddress"
android:text="#={viewModel.emailText}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/passwordTextInputLayout"
style="#style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
app:errorText="#{viewModel.passwordErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/emailTextInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/password"
android:inputType="textPassword"
android:text="#={viewModel.passwordText}" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="#+id/createAccountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:text="#string/create_one"
android:textColor="#color/textBlue"
app:layout_constraintBottom_toBottomOf="#+id/noAccountTextview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/noAccountTextview"
app:layout_constraintTop_toTopOf="#+id/noAccountTextview" />
<!-- **here is the important include**-->
<include
android:id="#+id/here_must_be_id_or_no_databinding"
android:visibility="#{viewModel.isLoading ? View.VISIBLE : View.GONE}"
layout="#layout/loading_state" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
i included the whole XML for clarification.
then in your view model add this
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SignInViewModelFactory(private val repository: NoteRepository) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SignInViewModel(repository) as T
}
class SignInViewModel(val repository: NoteRepository) : ViewModel() {
private val _isLoading = MutableLiveData(true)
val emailText = MutableLiveData("")
val passwordText = MutableLiveData("")
val isLoading: LiveData<Boolean> = _isLoading
fun logIn() {
//start loading, this will make the view start loading directly
_isLoading.value = true
if (isValidInput()) {
val res = repository.logIn(LoginUser(emailText.value!!, passwordText.value!!))
}//remove loading view
_isLoading.value = false
}
//code ..
}
notice that you are observing isLoading variable inside the XML so whenever its value is changed the view will observe the change and start act on it.