I created an abstract GlobalActivity extending AppCompatActivity and a GlobalViewModel extending ViewModel, in order to have some LiveData always ready to show Dialog messages and Toast messages, as well as displaying and hiding a ProgressBar. Problem is that the LoginActivity is not observing the LiveData object I mentioned above, so is not reacting to changes nor calls. Here is my code:
GlobalActivity:
public abstract class GlobalActivity extends AppCompatActivity {
protected GlobalViewModel mGlobalViewModel = new GlobalViewModel();
private Consumer<Throwable> errorHandler = throwable -> {
Timber.e(throwable);
DialogUtils.showOneButtonDialog(this, R.string.unexpected_error, null);
};
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RxJavaPlugins.setErrorHandler(this.errorHandler);
setUpBasicViewModel();
mGlobalViewModel.getDialogMessage().observe(this, mssg -> DialogUtils.showOneButtonDialog(GlobalActivity.this, mssg, null));
mGlobalViewModel.getToastMessage().observe(this, mssg -> DialogUtils.showMessage(mssg));
mGlobalViewModel.getIsLoading().observe(this, bool -> setLoadingState(bool));
}
public abstract void setLoadingState(boolean bool);
public abstract void setUpBasicViewModel();
}
GlobalViewModel:
public class GlobalViewModel extends ViewModel {
protected MutableLiveData<String> dialogMessage = new MutableLiveData<>();
protected MutableLiveData<String> toastMessage = new MutableLiveData<>();
protected SingleLiveEvent<Boolean> isLoading = new SingleLiveEvent<>();
public GlobalViewModel(){}
public MutableLiveData<String> getDialogMessage() {
return dialogMessage;
}
public MutableLiveData<String> getToastMessage() {
return toastMessage;
}
public SingleLiveEvent<Boolean> getIsLoading() {
return isLoading;
}
}
LoginActivity:
public class LoginActivity extends GlobalActivity {
private LoginViewModel mLoginViewModel;
private ActivityLoginBinding mDataBinding;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginViewModel = new ViewModelProvider(this, new LoginViewModelFactory()).get(LoginViewModel.class);
mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
mDataBinding.setLifecycleOwner(this);
mDataBinding.setViewModel(mLoginViewModel);
}
#Override
public void setLoadingState(boolean bool) {
mDataBinding.progressBar.setVisibility(mDataBinding.progressBar.isShown() ? View.GONE : View.VISIBLE);
}
#Override
public void setUpBasicViewModel() {
mGlobalViewModel = ViewModelProviders.of(this).get(GlobalViewModel.class);
}
...
}
Related
I have a Login Fragment which uses API call to login. I use mvvm and databinding to bind views with viewmodel. In viewmodel Login Response via retrofit is observed in viewmodel which uses RxJava.
I need to observe the retrofit response in the loginFragment, which is not get observed when retrofit response came. Following are the fragment and viewmodel code. I need retrofit response to pass to fragment or fragment get automatically observe response.
public class LoginFragment extends Fragment {
private LoginViewModel mLoginViewModel;
private Observable<LoginResult> dataObservable;
public static String TAG = LoginFragment.class.getSimpleName();
public Disposable disposable;
public static Fragment LoginFragmentInstance() {
Log.e(TAG, "LoginFragmentInstance: " );
Fragment fragment = new LoginFragment();
return fragment;
}
public LoginFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
FragmentLoginBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false);
mLoginViewModel = new LoginViewModel(getActivity());
//setViewModel method name changes based on variable name declared in XML
//mLoginViewModel.loginResult.observeO
dataObservable= mLoginViewModel.loginResult;
disposable = dataObservable.subscribe(new Consumer<LoginResult>() {
#Override
public void accept(LoginResult result) throws Exception {
Log.d("TAG", result.toString());
}
});
binding.setViewModel(mLoginViewModel);
return binding.getRoot();
}
#Override
public void onDestroy() {
mLoginViewModel.destroy();
disposable.dispose();
super.onDestroy();
}
}
ViewModel File
public class LoginViewModel {
private static final String TAG = "LoginViewModel";
public ObservableField<String> userName = new ObservableField<>();
public ObservableField<String> password = new ObservableField<>();
public ObservableField<String> email = new ObservableField<>();
public ObservableField<String> userNameErr = new ObservableField<>();
public ObservableField<String> passwordErr = new ObservableField<>();
public ObservableField<String> emailErr = new ObservableField<>();
public Observable<LoginResult> loginResult = new Observable<LoginResult>() {
#Override
protected void subscribeActual(Observer<? super LoginResult> observer) {
}
};
public ObservableField<Boolean> enableLogin;
private CompositeDisposable myCompositeDisposable = new CompositeDisposable();
private HashMap<String, String> loginApiParams;
public Action signIn;
public Context context;
public LoginViewModel(final Context context) {
this.context = context;
Observable result = Observable.combineLatest(FieldUtils.toObservable(userName), FieldUtils.toObservable(password),
new BiFunction() {
#Override
public Object apply(Object userName, Object password) throws Exception {
int failCount = 0;
if (!InputValidator.validateMobileno(userName.toString())) {
++failCount;
userNameErr.set(context.getResources().getString(R.string.mobileno_incorrect));
} else {
userNameErr.set("");
}
if (!InputValidator.validatePassword(password.toString())) {
++failCount;
passwordErr.set(context.getResources().getString(R.string.password_incorrect));
} else {
passwordErr.set("");
}
return failCount == 0;
}
});
enableLogin = FieldUtils.toField(result);
signIn = new Action() {
#Override
public void run() throws Exception {
Log.d(TAG, "signIn button clicked");
loginCall();
}
};
}
private void loginCall() {
loginApiParams = new HashMap<>();
// loginApiParams.put(, paymentType.toString())
loginApiParams.put(ApiParameterKeyConstants.MOBILE,userName.get());
loginApiParams.put(ApiParameterKeyConstants.PASSWORD, password.get());
UserApi usersService = ApiService.INSTANCE.apiCall();
Disposable disposable = usersService.getLogin(loginApiParams)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<LoginResult>() {
#Override
public void accept(LoginResult result) throws Exception {
loginResult = Observable.just(result);
//loginResult.subscribe()
//loginResult = result ;
//Log.d(TAG, "Login Successfull");
}
}, new Consumer<Throwable>()
{
#Override
public void accept(Throwable throwable) throws Exception {
Log.d(TAG, "Login Failed");
}
});
myCompositeDisposable.add(disposable);
}
}
It seems like you are re-assigning the loginResult an Observable in your loginCall method of the ViewModel instead of passing the result to its Observers.
You should try calling loginResult.onNext(result) or loginResult.onComplete(result) instead of loginResult = Observable.just(result);
I am trying to instantiate UserViewModel in my activity however it keeps giving me a java.lang.RuntimeException: Cannot create an instance of viewmodel class kindly assist.
This is how my ViewModel looks like
public class UserViewModel extends AndroidViewModel {
private NodeAuthService api;
private SharedPreferences pref;
private static MutableLiveData<List<User>> userDetails = new MutableLiveData<>();
public UserViewModel(#NonNull Application application) {
super(application);
api = AuthRetrofitClient.getInstance().create(NodeAuthService.class);
}
private String email = pref.getString("email", "");
public void loadUser(){
Call<List<User>> call;
call = api.getUser(email);
call.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
userDetails.postValue(response.body());
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Log.d("USER",t.getMessage());
}
});
}
public MutableLiveData<List<User>>getUserDetails(){
return userDetails;
}
}
This is how my activity is setup
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.navigation_drawer);
String nameVm;
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.loadUser();
userViewModel.getUserDetails().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
if (users != null){
for (int i = 0; i<users.size(); i++){
nameVm = String.valueOf(users.get(0));
}
}
}
});
}
Create ViewModelFactory class
public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
public MyViewModelFactory(Application application) {
mApplication = application;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
// Replace UserViewModel → with whatever or however you create your ViewModel
return (T) new UserViewModel(mApplication);
}
}
and init ViewModel like
UserViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication())).get(UserViewModel.class);
I would like to ask is it possible to make 2 or more DataBindingComponent class in android? because i want to escape the static method in binding so i try to use the injection with DataBindingComponent but I got the error of Class 'AppDataBindingComponent' must be either be declared abstract or implement abstract method 'getLoginViewDataBinding' in 'DataBindingComponent' because of this error I can't make the non-static one.
this is the class which i got the problem
public class AppDataBindingComponent implements android.databinding.DataBindingComponent {
#Override
public RecyclerViewDataBinding getRecyclerViewDataBinding() {
return new RecyclerViewDataBinding();
}
}
First binding class
public class RecyclerViewDataBinding {
#BindingAdapter({"app:adapter", "app:data"})
public void bind(RecyclerView recyclerView, DataAdapter adapter, List<DataModel> data) {
recyclerView.setAdapter(adapter);
adapter.updateData(data);
}
}
Second Binding Class
public class LoginViewDataBinding {
#BindingAdapter({"validation", "errorMsg"})
public void setErrorEnable(TextInputLayout textInputLayout, StringRule stringRule,
final String errorMsg) {
Observable<CharSequence> textObservable = RxTextView.textChanges(
Objects.requireNonNull(textInputLayout.getEditText()));
compositeDisposable.add(textObservable
.map(charSequence -> {
......
})
.distinctUntilChanged()
.replay(1).refCount()
.subscribe());
}
}
In the Main Class I call the DataBindingComponent
public class MainActivity extends AppCompatActivity {
private DataViewModel dataViewModel;
private ActivityMainListMvvmBinding activityBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bind();
}
private View bind() {
activityBinding = DataBindingUtil
.setContentView(this, R.layout.activity_main_list_mvvm, new AppDataBindingComponent());
dataViewModel = new DataViewModel();
activityBinding.setViewModel(dataViewModel);
return activityBinding.getRoot();
}
}
The problem is solved if I put getLoginViewDataBinding
public class AppDataBindingComponent implements android.databinding.DataBindingComponent {
#Override
public RecyclerViewDataBinding getRecyclerViewDataBinding() {
return new RecyclerViewDataBinding();
}
#Override
public LoginViewDataBinding getLoginViewDataBinding() {
return null;
}
}
the answers that I want is somehow like this: (is this possible?)
public class AppDataBindingComponent implements android.databinding.DataBindingComponent {
#Override
public RecyclerViewDataBinding getRecyclerViewDataBinding() {
return new RecyclerViewDataBinding();
}
}
public class LoginDataBindingComponent implements android.databinding.DataBindingComponent {
#Override
public LoginViewDataBinding getLoginViewDataBinding() {
return null;
}
}
What about this:
public class DataBindingComponent<T> implements android.databinding.DataBindingComponent {
private T activity;
public DataBindingComponent(T activity) {
this.activity = activity;
}
public LoginViewDataBinding getLoginViewDataBinding() {
return (LoginViewDataBinding) activity;
}
public RecyclerViewDataBinding getRecyclerViewDataBinding() {
return (RecyclerViewDataBinding) activity;
}
}
And than create in both of your class's:
new AppDataBindingComponent(this)
I have done like docs here but Live data'a value is not changing. Please tell me what am i doing wrong.
MainActivity
public class MainActivity extends AppCompatActivity {
private NameViewModel mModel;
private ActivityMainBinding binding;
int index = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.button.setOnClickListener((v) -> {
mModel.getCurrentName().setValue("Test");
});
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
final Observer<String> nameObserver = (text) -> {
binding.textInputLayout.getEditText().setText(text);
};
mModel.getCurrentName().observe(this, nameObserver);
}
}
NameViewModel.java
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
return new MutableLiveData<>();
}
return mCurrentName;
}
}
This is because, your logic returns new instance of mCurrentName each time. Please use the following function.
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
// Ensure there is only 1 instance of mCurrentName
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
}
A much better and safer way (reduce chance of making such mistake), is to initialize mCurrentName in constructor, and mark it as final.
public class NameViewModel extends ViewModel {
private final MutableLiveData<String> mCurrentName;
public NameViewModel() {
mCurrentName = new MutableLiveData<>();
}
public MutableLiveData<String> getCurrentName() {
return mCurrentName;
}
}
I have an activity with a viewpager of fragments filled with results of an api call:
ArtistFragment.java
public static ArtistFragment newInstance(String artistName, String imageUrl) {
ArtistFragment artistFragment = new ArtistFragment();
Bundle args = new Bundle();
args.putString(ARTIST_NAME, name);
args.putString(IMAGE_URL, imageUrl);
artistFragment.setArguments(args);
return artistFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BaseActivity activity = BaseActivity.get(getActivity());
AppComponent appComponent = activity.getAppComponent();
appComponent.inject(this);
imageUrl = getArguments().getString(IMAGE_URL);
title = getArguments().getString(TITLE);
}
MainActivity.java
public class MainActivity extends BaseActivity {
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appComponent.inject(this);
}
#Override
public void onResume() {
super.onResume();
subscriptions.add(client.searchArtists("Impressionist")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Artist>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Toast.makeText(getApplicationContext(),
e.getMessage(), Toast.LENGTH_LONG)
.show();
}
#Override
public void onNext(List<Artist> artists) {
ArtistPagerAdapter pagerAdapter = new
ArtistPagerAdapter(getSupportFragmentManager(),
artists);
viewPager.setAdapter(pagerAdapter);
}
}));
}
BaseActivity.java:
public class BaseActivity extends AppCompatActivity {
#Inject CompositeSubscription subscriptions;
AppComponent appComponent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent = MyApplication.get(this)
.getAppComponent();
appComponent.inject(this);
}
public static BaseActivity get(Context context) {
return (BaseActivity) context;
}
public AppComponent getAppComponent() {
return appComponent;
}
MyApplication.java
public class MyApplication extends Application {
private AppComponent appComponent;
public static MyApplication get(Context context) {
return (MyApplication) context.getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
ArtistPagerAdapter:
public class ArtistPagerAdapter extends FragmentStatePagerAdapter {
private List<Artist> results;
public ArtistPagerAdapter(FragmentManager fragmentManager,
List<Artist> results) {
super(fragmentManager);
this.results = results;
}
#Override
public int getCount() {
return results.size();
}
#Override
public Fragment getItem(int position) {
Artist artist = results.get(position);
String name = artist.getFullName();
String imageUrl = artist.getImageUrl();
return ArtistFragment.newInstance(name, imageUrl);
}
}
ApplicationComponent.java
#Component(modules = AppModule.class)
#Singleton
public interface AppComponent {
void inject(BaseActivity activity);
void inject(MainActivity activity);
void inject(ArtistFragment fragment);
}
I added log statements in the Activity and Fragment onCreate. If I rotate screen, why is the fragment's onCreate called prior to activity's onCreate?
put your code fragments in
onActivityCreated()
rather than
onCreate()
usually this happens when your memory is low and the activity is recycled.