Pass object between fragments using shared ViewModel - java

I'm trying to insert a object (repo) into the database and pass it to the next fragment.
I get the id of the inserted object and then I try to get the entity by the id, so the object includes the id.
onLoaded() should set the object in the ViewModel so I can use it in the next fragment.
The problem: Sometimes onLoaded seems to be called late or not at all. And then I can't add more information in the second fragment.
How can I make sure 'onLoaded' is called before I use the second fragment?
Or is there a better approach I should use? Maybe only passing the id in the ViewModel?
Fragment 1:
private void actionCloneRepo(#NonNull View view) {
TextInputEditText edittext_username = view.findViewById(R.id.edittext_username);
TextInputEditText edittext_token = view.findViewById(R.id.edittext_token);
TextInputEditText edittext_git_name = view.findViewById(R.id.edittext_git_name);
TextInputEditText edittext_git_email = view.findViewById(R.id.edittext_git_email);
Repo repo = new Repo(
Objects.requireNonNull(edittext_url.getText()).toString(),
Objects.requireNonNull(edittext_username.getText()).toString(),
Objects.requireNonNull(edittext_token.getText()).toString(),
Objects.requireNonNull(edittext_git_name.getText()).toString(),
Objects.requireNonNull(edittext_git_email.getText()).toString()
);
repoViewModel = new ViewModelProvider(requireActivity()).get(RepoViewModel.class);
int id = (int) repoViewModel.insert(repo);
repoViewModel.getRepoById(id).observe(getViewLifecycleOwner(), this::onLoaded);
NavHostFragment.findNavController(AddProject1Fragment.this)
.navigate(R.id.action_AddProject1Fragment_to_AddProject2Fragment);
}
private void onLoaded(Repo repo){
repoViewModel.setCurrentRepo(repo);
}
ViewModel:
public class RepoViewModel extends AndroidViewModel {
private final RepoRepository repoRepository;
private final LiveData<List<Repo>> allRepos;
private Repo currentRepo;
public RepoViewModel(Application application) {
super(application);
repoRepository = new RepoRepository(application);
allRepos = repoRepository.getAllRepos();
}
public Repo getCurrentRepo() {
return currentRepo;
}
public void setCurrentRepo(Repo currentRepo) {
this.currentRepo = currentRepo;
}
public LiveData<Repo> getRepoById(int id) {
return repoRepository.getRepoById(id);
}
public LiveData<List<Repo>> getAllRepos() {
return allRepos;
}
public long insert(Repo repo) {
return repoRepository.insert(repo);
}
public void update(Repo repo) {
repoRepository.update(repo);
}
public void delete(Repo repo) {
repoRepository.delete(repo);
}
}

Related

Getting data from Repository to Viewmodel without passing observer to the activity

Why i should pass the LiveData to the activity to get the value of allQuestions. I am a little bit confused.
I tried to get the List of allQuestions in QuestionViewModel from the repo directly but it didn't work.
This is what's in my brain.
public class QuestionViewModel extends AndroidViewModel {
private Repository repo;
private List<QuestionModel> allQuestions;
public QuestionViewModel(#NonNull Application application) {
super(application);
repo = new Repository(application);
allQuestions = repo.getAllQuestions().getValue(); //This line did not work for me (︶︹︺)
}}
What i did do to get it work.
ViewModel
public class QuestionViewModel extends AndroidViewModel {
private Repository repo;
private LiveData<List<QuestionModel>> allQuestions;
public List<QuestionModel> CurrentListOfQues;
public QuestionViewModel(#NonNull Application application) {
super(application);
repo = new Repository(application);
allQuestions = repo.getAllQuestions();
}
public LiveData<List<QuestionModel>> GetAllQues() {
if (allQuestions == null) {
allQuestions = new LiveData<List<QuestionModel>>() {
#Override
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<? super List<QuestionModel>> observer) {
super.observe(owner, observer);
}
};
}
return allQuestions;
}
public void setCurrent(List<QuestionModel> CurrentListOfQues) {
if (this.CurrentListOfQues == null) {
this.CurrentListOfQues = CurrentListOfQues;
}
}
Question Activity
QVM = new ViewModelProvider(this).get(QuestionViewModel.class);
QVM.GetAllQues().observe(this, new Observer<List<QuestionModel>>() {
#Override
public void onChanged(List<QuestionModel> allQuestions) {
QVM.setCurrent(allQuestions);
}
});
Is this the right way to get the list in my viewmodel? Why allQuestions = repo.getAllQuestions().getValue(); is always null

How to pass an intent variable to retrofit using Android Pagination Library

I am implementing android pagination library in my app and would like to pass "id" of an item from my activity to the data source where my network call is made
AddCommentActivity.java
//I want to pass this string to the network call.
String image_id = getIntent().getStringExtra("image_id");
CommentViewModel commentViewModel = new ViewModelProvider(this).get(CommentViewModel.class);
CommentDataSource.java
public class CommentDataSource extends PageKeyedDataSource<Long, Comment> {
public CommentDataSource(){
progress_bar = new MutableLiveData<>();
}
#Override
public void loadInitial(#NonNull final LoadInitialParams<Long> params, #NonNull final LoadInitialCallback<Long, Comment> callback) {
RestApi restApi = RetrofitApi.create();
Call<CommentResponse> call = restApi.getComments(FIRST_PAGE, "I want the image_id from activity here");
call.enqueue(new Callback<CommentResponse>() {
#Override
public void onResponse(Call<CommentResponse> call, Response<CommentResponse> response) {
}
CommentDataSourceFactory.java
public class CommentDataFactory extends DataSource.Factory<Long, Comment> {
public MutableLiveData<CommentDataSource> commentLiveDataSource = new MutableLiveData<>();
public CommentDataFactory() {
}
#Override
public DataSource<Long, Comment> create() {
CommentDataSource commentDataSource = new CommentDataSource();
commentLiveDataSource.postValue(commentDataSource);
return commentDataSource;
}
CommentViewModel.java
public class CommentViewModel extends ViewModel {
public LiveData<PagedList<Comment>> commentPagedList;
public LiveData<CommentDataSource> liveDataSource;
public LiveData progressBar;
public CommentViewModel(){
CommentDataFactory commentDataFactory = new CommentDataFactory();
liveDataSource = commentDataFactory.commentLiveDataSource;
progressBar = Transformations.switchMap(liveDataSource, CommentDataSource::getProgressBar);
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(CommentDataSource.PAGE_SIZE)
.build();
commentPagedList = new LivePagedListBuilder<>(commentDataFactory, config).build();
}
public LiveData<PagedList<Comment>> getCommentData(){
return commentPagedList;
}
public void getRefreshedData(){
getCommentData().getValue().getDataSource().invalidate();
}
}
How to do that.? I checked Passing variable to paging library class which is exactly what I want to do but I dont understand it and the code gives errors. Errors such as
Cannot create an instance of class CommentViewModel
CommentViewModel has no zero argument constructor
Okay do:
commentViewmodel1.getCommentData().observe(this, new Observer<PagedList<Comments>>(){
#Override
public void onChanged(PagedList<Comment>
comments){
adapter.submitList(comments);
}
});

Android MVVM/Repository how to force LiveData to update from repository?

here is my problem:
i have used MVVM/Repository design pattern like this:
Activity -(Observes)-> ViewModel's LiveData -> Repository -> WebService API (GET Resource)
i have another calls for UPDATING Resource to WebService.
Problem:
after changing resource on the server. how i can make the Resource livedata to update itself with new servers data
i want to force it fetch data from server again because some other data may have been changed.
and i dont want to use local database (Room) and change it because my server data might be changed. and they need to fetch each time.
The Only solution passed my Mind was to create a Livedata Source (as dataVersion) to it.
and increment it after every update like this (pseudo code):
dataVersion = new MutableLiveData();
dataVersion.setValue(0);
// my repository get method hasnt anything to do with the dataVersion.
myData = Transformation.switchmap(dataVersion, versionNum -> { WebServiceRepo.getList() });
and how dataVersion should get updated in ViewModel.
You could extend MutableLiveData to give it manual fetch functionality.
public class RefreshLiveData<T> extends MutableLiveData<T> {
public interface RefreshAction<T> {
private interface Callback<T> {
void onDataLoaded(T t);
}
void loadData(Callback<T> callback);
}
private final RefreshAction<T> refreshAction;
private final Callback<T> callback = new RefreshAction.Callback<T>() {
#Override
public void onDataLoaded(T t) {
postValue(t);
}
};
public RefreshLiveData(RefreshAction<T> refreshAction) {
this.refreshAction = refreshAction;
}
public final void refresh() {
refreshAction.loadData(callback);
}
}
Then you can do
public class YourViewModel extends ViewModel {
private RefreshLiveData<List<Project>> refreshLiveData;
private final GithubRepository githubRepository;
private final SavedStateHandle savedStateHandle;
public YourViewModel(GithubRepository githubRepository, SavedStateHandle savedStateHandle) {
this.githubRepository = githubRepository;
this.savedStateHandle = savedStateHandle;
refreshLiveData = Transformations.switchMap(savedStateHandle.getLiveData("userId", ""), (userId) -> {
githubRepository.getProjectList(userId);
});
}
public void refreshData() {
refreshLiveData.refresh();
}
public LiveData<List<Project>> getProjects() {
return refreshLiveData;
}
}
And then repository can do:
public RefreshLiveData<List<Project>> getProjectList(String userId) {
final RefreshLiveData<List<Project>> liveData = new RefreshLiveData<>((callback) -> {
githubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
callback.onDataLoaded(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
}
});
});
return liveData;
}

Android Architecture SingleLiveEvent and EventObserver Practicle Example in Java

I try to make sample login page with two fields (username, password) and save button with android architecture component, using android data binding, validating the data in viewmodel and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent and EventObserver, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
#Inject
public LoginViewModel(#NonNull AppDatabase appDatabase,
#NonNull JobPortalApplication application,
#NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
#Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver in above case? Any practical example?
Check out below example about how you can create single LiveEvent to observe only one time as LiveData :
Create a class called Event as below that will provide our data once and acts as child of LiveData wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver class like below so that we don't end up placing condition for checking about Event handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event("")); is meant for repository when you've got response from API (Remember to return same LiveData type you've taken in VM instead of SingleLiveEvent class though).
So, let's say you've created LiveData in ViewModel like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
#Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
#Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
I understand that Google gives the guidelines to use LiveData between the ViewModel and UI but there are edge cases where using LiveData as a SingleLiveEvent is like reinventing the wheel. For single time messaging between the view model and user interface we can use the delegate design pattern. When initializing the view model in the activity we just have to set the activity as the implementer of the interface. Then throughout our view model we can call the delegate method.
Interface
public interface Snackable:
void showSnackbarMessage(String message);
UI
public class MyActivity extends AppCompatActivity implements Snackable {
private MyViewModel myViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
this.myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
this.myViewModel.setListener(this);
}
#Override
public void showSnackbarMessage(String message) {
Toast.makeText(this, "message", Toast.LENGTH_LONG).show();
}
}
View Model
public class MyViewModel extends AndroidViewModel {
private Snackable listener;
public MyViewModel(#NonNull Application application) {
super(application);
}
public void setListener(MyActivity activity){
this.listener = activity;
}
private void sendSnackbarMessage(String message){
if(listener != null){
listener.showSnackbarMessage(message);
}
}
private void anyFunctionInTheViewModel(){
sendSnackbarMessage("Hey I've got a message for the UI!");
}
}

How to use constructor in Realm v0.87.1 on Android

I want use Realm version 0.87.1 for Database in android, and i create provider class to save values but when use this class constructor in main class (activity) show me this error, please see error from this image : Error image Link
Task_Provider class :
public class Task_Provider extends RealmObject {
public Task_Provider() {
}
public String getAddTask() {
return addTask;
}
public void setAddTask(String addTask) {
this.addTask = addTask;
}
public long getAdded_date() {
return added_date;
}
public void setAdded_date(long added_date) {
this.added_date = added_date;
}
public long getWhen_date() {
return when_date;
}
public void setWhen_date(long when_date) {
this.when_date = when_date;
}
public boolean isComplete() {
return complete;
}
public void setComplete(boolean complete) {
this.complete = complete;
}
public Task_Provider(String addTask, long added_date, long when_date, boolean complete) {
this.addTask = addTask;
this.added_date = added_date;
this.when_date = when_date;
this.complete = complete;
}
private String addTask;
#PrimaryKey
private long added_date;
private long when_date;
private boolean complete;
}
Main Activity (Button setOnClickListener) :
String addTask = dialog_edit_task.getText().toString();
long now = System.currentTimeMillis();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(getActivity()).build();
Realm.setDefaultConfiguration(realmConfiguration);
Realm realm = Realm.getDefaultInstance();
Task_Provider task_provider = new Task_Provider(addTask, now, 0, false);
realm.beginTransaction();
realm.copyToRealm(task_provider);
realm.commitTransaction();
realm.close();
How can i fix this problem? tnx all <3
Did you try cleaning your project? I got with that issue, but disappeared as i cleaned it. You have an empty constructor already :)
Otherwise, you can try to create the object realm defaults.
Because copyToRealm... your Task_Provider object is already a realm object, since you are extending it on the class declaration.
Task_Provider task = realm.createObject(Task_Provider.class);
task.setAddTask("");
...
Hope it helps!

Categories