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.
Related
I have a question about two-way data binidng implemented by MutableLiveData in ViewModel Class for and EditText.
If I define a LoginViewModel Class for a user, which is consisted of User, email and password as follows:
class LoginViewModel : ViewModel() {
val user = MutableLiveData<User>()
}
and
data class User(var email: String, var password: String)
when I rotate the phone (configuration changes occurs), the data entered will be gone.
<?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="loginViewModel"
type="com.udacity.shoestore.screens.login.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/fragment_horizontal_margin"
android:paddingTop="#dimen/fragment_vertical_margin"
android:paddingRight="#dimen/fragment_horizontal_margin"
android:paddingBottom="#dimen/fragment_vertical_margin"
tools:context=".screens.login.LoginFragment">
<TextView
android:id="#+id/email_text"
style="#style/title_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/layout_margin"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/str_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/email_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:autofillHints=""
android:hint="#string/str_email_hint"
android:inputType="textEmailAddress"
android:text="#={loginViewModel.user.email}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/email_text" />
<TextView
android:id="#+id/password_text"
android:layout_width="0dp"
style="#style/title_style"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/str_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/email_edit" />
<EditText
android:id="#+id/password_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:autofillHints=""
android:hint="#string/str_password_hint"
android:inputType="textPassword"
android:text="#={loginViewModel.user.password}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/password_text" />
<Button
android:id="#+id/sign_up_button"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginEnd="#dimen/small_margin"
android:text="#string/str_sign_up"
app:layout_constraintBaseline_toBaselineOf="#+id/sign_in_button"
app:layout_constraintEnd_toStartOf="#+id/sign_in_button"
app:layout_constraintStart_toStartOf="#+id/password_edit" />
<Button
android:id="#+id/sign_in_button"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="#dimen/small_margin"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginBottom="#dimen/medium_margin"
android:text="#string/str_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/password_edit"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/sign_up_button"
app:layout_constraintTop_toBottomOf="#+id/password_edit"
app:layout_constraintVertical_bias="0.524" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I wonder if there is something wrong about defining the User data class or using it in ViewModel or something else, but it doesn't work.
On the other hand, if I define the elemnts of user seperately in ViewModel, it works:
class LoginViewModel : ViewModel() {
val email = MutableLiveData<String>()
val password = MutableLiveData<String>()
}
and of course some changes in xml:
android:text="#={loginViewModel.email}"
and
android:text="#={loginViewModel.password}"
any idea?
My ShoeStore project in GitHub
I use this exact same implementation in my app.
Did you set binding lifecycleOwner in your activity or fragment ?
When using viewmodels, this is actually how I do it.
class LoginViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
}
In my fragment onCreateView
override
fun onCreateView(...){
val binding = FragmentBinding.inflate(...)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
...
}
I'll check your project on GitHub and let you know if anything...
Sorry for the late reply. I was busy playing with Jetpack Compose ;)
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.
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’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.
I have a model CricketModel
public class CricketModel extends BaseObservable{
private String score;
#Bindable
public String getScore(){
return score
}
public void setScore(String s){
score=s;
notifyPropertyChanged(BR.score);
}
}
When I call API I get JSONArray, I convert them to CricketModel and add it to a CricketModel list. And pass this list to adapter to display them in the Recycler view.
This is my item_cricket.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="cm"
type="com.panasonic.arbohub.cricket.model.CricketModel" />
</data>
<LinearLayout xmlns:app="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="#+id/cv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/dp_8"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="3dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_two_s_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/tv_one_p_score"
android:layout_marginEnd="#dimen/dp_16"
android:layout_marginTop="#dimen/dp_8"
android:layout_toStartOf="#+id/rl_team_two"
android:textSize="#dimen/sp_12"
android:textStyle="bold"
android:text="#{cm.score}"
android:visibility="visible" />
</FrameLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</layout>
This is my ViewHolder
private class CricketViewHolder extends RecyclerView.ViewHolder {
ItemCricketBinding itemCricketBinding;
public CricketViewHolder(ItemCricketBinding binding) {
super(binding.getRoot());
itemCricketBinding = binding;
}
public void bind(CricketModel cm){
itemCricketBinding.setCm(cm);
itemCricketBinding.executePendingBindings();
}
}
In my CricketAdapter.java
holder.bind(cricketModelList.get(position));
API updates once in 1 mi, I'm replacing the cricketModelList with new data that is coming from the API but data is not reflecting in the UI.
Have followed this- https://medium.com/google-developers/android-data-binding-recyclerview-db7c40d9f0e4
Update:
After api has been called. I added notifyDataSetChanged() onClickOfthe item view, then new data is reflecting. So model has the new data but its not notifying the UI.
you should set "itemCricketBinding.lifecycleOwner" in bind method, not the best practice and I'm not sure about the performance but if you wonder why that not work, That was the reason.