On PostsByLabelViewModel it have MutableLiveData<String> token this token is changed every scrolling of recyclerView, I need to observe on it in the PostsByLabelViewModel not from UI, because I tried to change it in recyclerView.addOnScrollListener and the app is freezing and hanged. Here's the code:
public class PostsByLabelViewModel extends ViewModel {
public static final String TAG = "PostsByLabelViewModel";
public MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String> finalURL = new MutableLiveData<>();
public MutableLiveData<String> token = new MutableLiveData<>();
public void getPostListByLabel() {
Log.e(TAG, finalURL.getValue());
PostsByLabelClient.getINSTANCE().getPostListByLabel(finalURL.getValue()).enqueue(new Callback<PostList>() {
#Override
public void onResponse(Call<PostList> call, Response<PostList> response) {
PostList list = response.body();
if (list.getItems() != null) {
Log.e(TAG, list.getNextPageToken());
token.setValue(list.getNextPageToken());
postListMutableLiveData.setValue(list);
}
}
#Override
public void onFailure(Call<PostList> call, Throwable t) {
}
});
}
}
I see there's an observe method on the ViewModel and I tried to use it like this
token.observe(PostsByLabelViewModel.this, new Observer<String>() {
#Override
public void onChanged(String s) {
token.setValue(s);
}
});
but I got runtime error
error: incompatible types: PostsByLabelViewModel cannot be converted to LifecycleOwner
token.observe(PostsByLabelViewModel.this, new Observer<String>() {
So how can I observe on the token on every change?
I need to observe on it in the PostsByLabelViewModel not from UI.
You might use observeForever. Just don't forget to call removeObserver when it is no longer needed.
...the app is freezing and hanged.
You're calling PostsByLabelClient.getINSTANCE().getPostListByLabel(finalURL.getValue()).enqueue on the main thread. Move it to a background thread.
Related
Hi I have this App where it displays movies from TMDB I am having an issue where I can't display a feedback to users that may lead them to keep unnecessarily waiting When my app starts without internet or server returns no data
public MainViewModel(#NonNull Application application) {
super(application);
AppDatabase appDatabase = AppDatabase.getInstance(this.getApplication());
favoriteMovies = appDatabase.favoriteDao().loadAllFavorites();
Call<ApiResults> call = Network.buildAPICall(Network.POPULAR);
call.enqueue(new Callback<ApiResults>() {
#Override
public void onResponse(Call<ApiResults> call, Response<ApiResults> response) {
if (response.message().contentEquals("OK")) {
popularMovies.setValue(response.body().getMovies());
} else {
Log.e(TAG, "Something unexpected happened to our request: " + response.message());
}
}
#Override
public void onFailure(Call<ApiResults> call, Throwable t) {
Log.i(TAG, "Something unexpected happened to our request: " );
Log.e(TAG, t.getMessage());
}
});
I want to display the message "Something unexpected happened to our request: " when there is no internet access to the mainActivity the problem is I can't display a toaster in the view model class
Here is my main Activity code snippet
public void setupViewModel() {
com.example.popularmovies.UI.MainViewModel viewModel = ViewModelProviders.of(this).get(com.example.popularmovies.UI.MainViewModel.class);
Log.i("Test",""+ viewModel);
viewModel.getFavoriteMovies().observe(this, new Observer<List<MovieData>>() {
#Override
public void onChanged(#Nullable List<com.example.popularmovies.Data.MovieData> favoriteEntries) {
Log.d(TAG, "Receiving changes from LiveData");
if (mSortOrder.contentEquals(FAVORITE)) {
List<com.example.popularmovies.Data.MovieData> movieList = new ArrayList<com.example.popularmovies.Data.MovieData>();
if (favoriteEntries != null) {
for (com.example.popularmovies.Data.MovieData fave : favoriteEntries) {
fave.setFavorite(1);
}
setAdapter(favoriteEntries);
}
}
}
});
viewModel.getTopRatedMovies().observe(this, new Observer<List<com.example.popularmovies.Data.MovieData>>() {
#Override
public void onChanged(#Nullable List<com.example.popularmovies.Data.MovieData> movieData) {
Log.i("Test",""+ movieData);
if (movieData != null && mSortOrder.contentEquals(com.example.popularmovies.Utils.Network.TOP_RATED)) {
setAdapter(movieData);
}
}
});
viewModel.getPopularMovies().observe(this, new Observer<List<com.example.popularmovies.Data.MovieData>>() {
#Override
public void onChanged(#Nullable List<com.example.popularmovies.Data.MovieData> movieData) {
Log.i("Test",""+ movieData);
if (movieData != null && mSortOrder.contentEquals(com.example.popularmovies.Utils.Network.POPULAR)) {
setAdapter(movieData);
}
}
});
}
Any suggestions how to do that?
Use LiveData, An Observable data holder class, also and Lifecycle aware in your case Activity Lifecycle.
Declare Variable
private MutableLiveData<String> toastMessageObserver = new MutableLiveData();
Set Value
toastMessageObserver.setValue("Something unexpected happened to our request: "+response.message()); // Whenever you want to show toast use setValue.
Getter Method
Define getter method in viewModel
public LiveData<String> getToastObserver(){
return toastMessageObserver;
}
In activity inside setupViewModel
viewModel.getToastObserver().observe(this, message -> {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
});
I am using MVVM pattern in which I am using SwipeRefresh layout to refresh recycler view in my layout.When I am pulling it then it continue to refresh even after method completed successfully.
Below is my code:
MainActivity.java
refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
userRepository.getUserList();
}
});
UserRepository.java
public void getUserList(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<User>> userList = apiService.getUser();
userList.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, final Response<List<User>> response) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
if(response.body() != null) {
List<User> list = response.body();
for (int i = 0; i < list.size(); i++) {
String id = list.get(i).get_id();
String names = list.get(i).getName();
String age = list.get(i).getAge();
User user = new User(id,names,age);
userDb.userDao().Insert(user);
}
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Toast.makeText(context,"Data inserted",Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
Toast.makeText(context,e.getMessage(),Toast.LENGTH_LONG).show();
}
});
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Toast.makeText(context,t.getMessage(),Toast.LENGTH_LONG).show();
}
});
}
Method fetching list is in another class UserRepository and I am calling method in another activity MainActivity.I am not getting any way how can I stop refreshing process.Someone please let me know a way to stop refreshing process.
Any help would be appreciated.
THANKS
To disable the progress dialog add this,
swipeLayout.setRefreshing(false);
I am trying to use realm database to display my api data. I want to display the company name, however the data is saids it is inserted in the log but cant seem to display the data on the UI. Here is the code..
Any help would be greatly appreciated with this problem. The variables are at the top and the problem is when it hits on success, ive written the code "write to DB", but it doesnt display the data but tells me the data has been inserted.
// Variables for the search input field and results TextViews.
private EditText mCompanyInput;
private TextView mTitleText;
private TextView mDescriptionText;
private TextView mOfficerText;
private TextView mTitleText1;
private TextView mDescriptionText1;
private OkHttpClient okHttpClient;
private static final String TAG = "MainActivity";
private Request request;
private String url = "https://api.companieshouse.gov.uk/search/companies?q=";
Button save;
TextView log;
Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCompanyInput = findViewById(R.id.companyInput);
log = findViewById(R.id.log);
mDescriptionText = findViewById(R.id.descriptionText);
mOfficerText = findViewById(R.id.officerText);
mTitleText1 = findViewById(R.id.titleText1);
mTitleText = findViewById(R.id.titleText);
mDescriptionText1 = findViewById(R.id.descriptionText1);
save = findViewById(R.id.searchButton);
realm = Realm.getDefaultInstance();
save.setOnClickListener(this);
}
public void onClick(View view){
okHttpClient = new OkHttpClient();
request = new Request.Builder().url(url).header("Authorization", "k6DNRbTp-AnQWn51JBz5VuPiTl8jv4_etdzoMyhf") .method("GET", null).build();
Log.d(TAG, "onClick:"+url);
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, e.getMessage());
}
#Override
public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG,response.body().string());
Log.d(TAG, "onResponse:"+response.code());
}
});
writeToDB(mCompanyInput.getText().toString().trim(), (mDescriptionText.getText().toString().trim()));
showData();
}
public void showData(){
RealmResults<Company> guests = realm.where(Company.class).findAll();
// Use an iterator to invite all guests
String op="";
for (Company guest : guests) {
op+=guest.getName();
op+=guest.getAppointments();
}
log.setText(op);
}
public void writeToDB(final String mTitleText1, final String mDescriptionText1){
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
Company user = new Company(mTitleText1, mDescriptionText1);
bgRealm.insert(user);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
writeToDB(mCompanyInput.getText().toString().trim(), (mOfficerText.getText().toString().trim()));
showData();
// Transaction was a success.
Log.v("Database", "Data Inserted");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
Log.e("Database", error.getMessage());
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
Why are you calling writeToDB() from the onSuccess() method? This will cause recursion and keep writing the same data into the realm. It's correct to call showData() from onSuccess(), but there's not much point calling it directly from onClick().
I think your problem though is that you're trying to update the UI from a thread: it's called from an async transaction thread and not the main thread. See this answer (and there are others you can find easily once you know the problem: Updating UI / runOnUiThread / final variables: How to write lean code that does UI updating when called from another Thread.
I'm working on a new project that implements MVVM. Can I use a viewmodel that is observed for two activities ? or should I make one viewmodel for each activity ?
public class FormViewModel extends AndroidViewModel {
/*
This is my only ViewModel in the project
*/
private UserRepository userRepository;
//linked fields in xml for lib Data Binding
public String name, lastName, address, age;
//variables observed in the views
public MutableLiveData<String> responseMessageInsertUpdate = new MutableLiveData<>();
public MutableLiveData<String> responseStartUserFormActivity = new MutableLiveData<>();
public MutableLiveData<String> responseMessageDelete = new MutableLiveData<>();
public FormViewModel(Application application) {
super(application);
userRepository = new UserRepository(application);
}
//get all users from database that implements RoomDataBase, it´s observed em MainActivity
//and update recyclerview when database receive any change
public LiveData<List<User>> getAllUsers() {
return userRepository.selectAllUsers();
}
/*
action of submit button defined (linked for lib Data Binding) in xml
makes change or user registration
*/
public void submitClick(User user) {
int idade = 0;
if (this.age != null) {
if (!this.age.isEmpty()) {
idade = Integer.parseInt(this.age);
}
}
if (user != null) {
user.setName(name);
user.setLastName(lastName);
user.setAddress(address);
user.setAge(idade);
} else {
user = new User(name, lastName, address, idade);
}
//validation logic
if (user.isFormValid()) {
if (user.getId() > 0) {
//update the user in the database
userRepository.updateUser(user);
//there is an observable of this MutableLiveData variable in UserFormActivity that shows this
//message in a toast for the User when received a value
responseMessageInsertUpdate.setValue("User data uploaded successfully.");
} else {
//insert the user on data base
userRepository.insertUser(user);
responseMessageInsertUpdate.setValue("User " + user.getName() + " stored successfully.");
}
} else {
responseMessageInsertUpdate.setValue("Please, correctly fill in all the fields of the form to confirm the registration.");
}
}
//action of btnNewForm linked for lib Data Binding in xml
public void newFormClick() {
/*
this MutableLiveData is observed for MainActivity and start a new UserFormActivity when receive
value when the btnNewForm is pressed
*/
responseStartUserFormActivity.setValue("startActivity");
}
//delete User from database
public void deleteUser(User user) {
if (user != null) {
userRepository.deleteUser(user);
/*
there is an observable of this MutableLiveData variable in MainActivity that shows this
message in a toast for the user when received a value (when an user is deleted from database)
*/
responseMessageDelete.setValue(user.getName() + " removed from list successfully.");
}
}
//this method is called on UserFormActivity to show more details of an existing user in activity fields
public void showDataUserInActivity(User user) {
//linked fields in xml for lib Data Binding that receive values from the object user
name = user.getName();
lastName = user.getLastName();
address = user.getAddress();
age = String.valueOf(user.getAge());
}
}
public class MainActivity extends AppCompatActivity {
/*
this activity shows all users in recyclerview
*/
private Context contexto = this;
private ActivityMainBinding binding;
private UserAdapter userAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
FormViewModel formViewModel = ViewModelProviders.of(this).get(FormViewModel.class);
binding.setViewModel(formViewModel);
createRecyclerView();
methodsViewModel();
}
//methods from ViewModel
private void methodsViewModel() {
//observer that update recyclerview when database receive any change
binding.getViewModel().getAllUsers().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(#Nullable List<User> pessoas) {
userAdapter.addUserToList(pessoas);
}
});
//observer that starts a new UserFormActivity when btnNewForm is pressed
//receive value in the method newFormClick from ViewModel
binding.getViewModel().responseStartUserFormActivity.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
startUserFormActivity();
}
});
//observer that shows a message in a toast when the user is deleted from database
//receive value in the method deleteUser from ViewModel
binding.getViewModel().responseMessageDelete.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String message) {
Toast.makeText(contexto, message, Toast.LENGTH_SHORT).show();
}
});
}
private void createRecyclerView() {
RecyclerView rvUser = binding.rvPessoas;
rvUser.setLayoutManager(new LinearLayoutManager(contexto));
userAdapter = new UserAdapter(contexto, itemClick());
rvUser.setAdapter(userAdapter);
}
private void startUserFormActivity() {
Intent intent = new Intent(contexto, UserFormActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
contexto.startActivity(intent);
}
private void startUserFormActivity(User user) {
Intent intent = new Intent(contexto, UserFormActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("user", user);
contexto.startActivity(intent);
}
private UserAdapter.ItemClick itemClick() {
return new UserAdapter.ItemClick() {
#Override
public void simpleClick(View view, final int position) {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(contexto);
String[] options = {"Update", "Delete"};
alertDialog.setItems(options, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
if (i == 0) {
//start a new UserFormActivity to change user attributes
startUserFormActivity(userAdapter.getUserFromList().get(position));
} else if (i == 1) {
//call the method deleteUser from ViewModel
binding.getViewModel().deleteUser(userAdapter.getUserFromList().get(position));
}
}
});
alertDialog.show();
}
};
}
}
public class UserFormActivity extends AppCompatActivity {
private Context context = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FormViewModel formViewModel = ViewModelProviders.of(this).get(FormViewModel.class);
final ActivityFormUserBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_form_user);
binding.setViewModel(formViewModel);
if (getIntent().getSerializableExtra("user") != null) {
User user = (User) getIntent().getSerializableExtra("user");
formViewModel.showDataUserInActivity(user);
//put user data in activity when action "update" is called in MainActivity
binding.setUser(user);
}
/*
Method from ViewModel
Observer that shows a message in a toast and close the activity when the user is storage or updated from database
receive value in the method submitClick from ViewModel
*/
formViewModel.responseMessageInsertUpdate.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
Toast.makeText(context, s, Toast.LENGTH_LONG).show();
if (s.contains("successfully")) {
finish();
}
}
});
}
}
Here is my ViewModel and my two activities for more details. As I said it's a ViewModel that is observed for two activities. This ViewModel calls a repository that updates, inserts and deletes user data as well as also updates e sends messages to the views.
It's completely OK to share a viewmodel among the views, in case if you're using the same data or it's a kind of centralised datastore.
Otherwise implement separate model for each view as it increases
code readability and hence efficiency.
Happy to provide personalised solution if you could post some of your
code snippets here. Happy coding
My IntentService is responsible for synching the local SQLite data to Firebase collection when Wifi network is available, however if I am trying this exact same code from my repository class its working perfectly but not working(Firestore add) from background IntentService.
Below is my code, can anybody help me with this. I'm stuck :(
public class DataSyncService extends IntentService {
private NotesDao mDao;
private FirebaseFirestore mFirestore;
public DataSyncService() {
super("DataSyncService");
}
#Override
protected void onHandleIntent(Intent intent) {
mDao = Room.databaseBuilder(getApplicationContext(), NotesDatabase.class, Constants.DATABASE_NAME)
.build()
.getNotesDao();
FirebaseApp firebaseApp = FirebaseApp.initializeApp(getApplicationContext());
if (firebaseApp != null) {
mFirestore = FirebaseFirestore.getInstance(firebaseApp);
sync();
}
}
/**
* Sync the current local persistence data with the network
*/
public void sync() {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
final List<Notes> offlineData = mDao.fetch();
for (Notes notes :
offlineData) {
Map<String, Object> note = new HashMap<>();
note.put(Constants.NOTE_TITLE, notes.getTitle());
note.put(Constants.NOTE_DESCRIPTION, notes.getDescription());
note.put(Constants.NOTE_TIMESTAMP, notes.getTimestamp());
note.put(Constants.NOTE_IMAGE_URL, notes.getImageUri());
mFirestore.collection(Constants.NOTES_COLLECTION).document(notes.getId()).set(note, SetOptions.merge());
}
}
});
thread.start();
}
}