I want to display Toast message on Retrofit failure in ViewModel Class - java

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();
});

Related

How to observe on LiveData in ViewModel?

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.

Android in-app billing Purchase dialog not showing after startPurchaseFlow() is called

Hello guys and good saturday everyone,
For the first time i'm implementing an in-app billing service in my app, the aim is to make a "Buy pro and remove ads" button inside app menu, quite simple actually.
Following some guides i made a BillingManager.java:
public class BillingManager implements PurchasesUpdatedListener {
private static final String TAG = "BillingManager";
private final BillingClient mBillingClient;
private final Activity mActivity;
public BillingManager(Activity activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
#Override
public void onPurchasesUpdated(int responseCode, List<Purchase> purchases) {
Log.i(TAG, "onPurchasesUpdated() response: " + responseCode);
}
public void startPurchaseFlow(String skuId, String billingType) {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setType(billingType).setSku(skuId).build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
Log.i(TAG, "Received command");
}}
and then i'm calling it inside MainActivity in the menu:
if (id == R.id.action_pro) {
BillingManager mbilling = new BillingManager(MainActivity.this);
mbilling.startPurchaseFlow("mysku", BillingClient.SkuType.INAPP);
return true;
}
But when i press the button corresponding to "action_pro" Nothing happens.
All the logs shows everything is fine, onBillingSetupFinished() response is always 0 and onPurchaseUpdated() response is alwasy -1.
There aren't any crashes or error, just nothing happens...
What can i do?
Any answer is as always highly appreciated!
Edit: i put a logger for debug and i se that if put int response = mBillingClient.launchBillingFlow(mActivity, builder.build());
Log.i(TAG, "Response code: "+ response);
i get the error: I/BillingManager: Response code: -1
So actually mBillingClient.launchBillingFlow(mActivity, builder.build()); throws -1 as result... Any ideas?

onPostExecute() not related to doInBackground() while debugging

I have a problem with my onPostExecute() method in AsyncTask class.
I have an SignupActivity:
public class SignupActivity extends AppCompatActivity implements SignupListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.signup_activity);
//new task, i pass context and interface to it
signup = new Signup(getApplicationContext(), this);
signupButon.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if(validate()) {
try {
//new task every click
Signup newSignup = new Signup(signup);
//here start AsyncTask
newSignup.execute(name, email, password).get();
} catch (Exception e) {
Toast.makeText(ERROR);
}
// if sign up succes, == true;
if(signupValid) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
}
}
});
}
// my own interface for getting result as bool from onPostExecute
#Override
public void onSignupPerformed(Boolean result){ this.signupValid = result; }
That implements my interface to catching result from onPostExecute():
public interface SignupListener{
void onSignupPerformed(Boolean result);
}
Now, AsyncTask that i trigger in code:
public class Signup extends AsyncTask<String, Boolean, Boolean> {
public Signup(Context context, SignupListener listener){
db = ApplicationDatabase.getDatabase(context);
this.context = context;
this.listener = listener;
}
public Signup(Signup signup){
//constructor to make new task based on first task
db = signup.db;
context = signup.context;
listener = signup.listener;
}
protected Boolean doInBackground(String... body){
try {
user = db.userDao().getUser(body[0], body[1], body[2]);
if (user == null) {
// user is null, so we can add new one to DB
db.userDao().insertUser(new User(body[0], body[1], body[2]));
return Boolean.TRUE; //signup go good, return true
} else {
return Boolean.FALSE; //signup go bad, return false
}
} catch(Exception e) { }
return null;
}
protected void onPostExecute(Boolean result) {
//catching result from doInBackground
listener.onSignupPerformed(result);
}
My question is, why when i first click on button, func return Boolean.TRUE but in SignupActivity signupValid variable is false (signup form not exit, but user is added to DB), but when i click signup button second time, ofc signup fail (because we make new user seconds ago) but signupValid change to true and Signup Form pass? I need to click SignupButton two times to finally exit form. Thanks for finding error in my code
EDIT:
I replaced .get() with Progress Dialog to block UI, but now i get Toast with not valid form even before AsyncTask for Signup do his job. And still, in first click signupValid is false even when from doInBackground() i get TRUE, on second click AsyncTask return FALSE but signupValid is changed to true
My UserDAO:
#Dao
public interface UserDao {
#Query("SELECT * FROM users WHERE email = :email AND password = :password AND username = :username")
User getUser(String username, String email, String password);
}
And ApplicationDatabase:
public abstract class ApplicationDatabase extends RoomDatabase {
public abstract UserDao userDao();
public static ApplicationDatabase getDatabase(final Context context){
if(INSTANCE == null){
synchronized (ApplicationDatabase.class){
if(INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), ApplicationDatabase.class, "database").build();
}
}
}
return INSTANCE;
}
private static volatile ApplicationDatabase INSTANCE;
If I understood the problem correctly - there is a race condition that makes the SignupActivity to fire the toast before the execution of Signup task is completed. Therefore:
signupButon.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if(validate()) {
try {
//new task every click
Signup newSignup = new Signup(signup);
//here start AsyncTask
newSignup.execute(name, email, password).get();
} catch (Exception e) {
Toast.makeText(ERROR);
}
}
}
});
While these lines:
// if sign up succes, == true;
if(signupValid) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
Should be a part of the listener (right now it seems that these lines are executed BEFORE the completion of your async task)
To clarify myself:
#Override
public void onSignupPerformed(Boolean result)
{
if(result) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
}

Room LiveData onChange called too fast. Many Activities stacked atop

#Dao
public interface LibraryCoverContentDao {
#Query("SELECT * FROM LibraryCoverContent where rush_id = :rush_id")
LiveData<List<LibraryCoverContent>> getContentsFromRushID(String rush_id);
#Query("DELETE FROM library_cover where rush_id = :rush_id")
void deleteContentsFromRushID(String rush_id);
#Insert(onConflict = REPLACE)
void insertCoverContents(LibraryCoverContent... contents);
}
I want to open another activity once a list LiveData> mLibraryCoverContents is not null.
I am inserting the items downloaded from a retrofit call one by one into the room database, so apparently, my startActivity() call for the next activity happens many a times and multiple-same activities are opened over this activity.
I want only a single activity on top by calling onChanged only after all items of the retrofit call are inserted into db.
Please see the following related code for reference:
public void openReadRushScreen(final int index) {
int count = mCoversList.size();
if(count > index){
mRushIDContent = mLibraryContentRepository.getContentsFromID(mCoversList.get(index).getRush_id());
mRushIDContent.observe(this, new Observer<List<LibraryCoverContent>>() {
#Override
public void onChanged(#Nullable List<LibraryCoverContent> libraryCoverContents) {
Toast.makeText(getActivity(), "ON CHANGED", Toast.LENGTH_SHORT).show();
if(libraryCoverContents!=null && libraryCoverContents.size()>0){
mRushIDContentsList = libraryCoverContents;
if(mRushIDContentsList.size()>0 && mRushIDContentsList.get(0).getRush_id().equals(mCoversList.get(index).getRush_id())){
mRushIDContentsList = new ArrayList<>();
startActivity(ReadRushActivity.getStartIntent(getActivity(), mCoversList.get(index).getRush_id(),
mCoversList.get(index).isRush_audio(),
mCoversList.get(index).getTitle()));
}
}
else {
if(mCoversList!=null && mCoversList.size()>index) getContent(mCoversList.get(index).getRush_id());
}
}
});
}
else Toast.makeText(getActivity(), "Empty Cover", Toast.LENGTH_SHORT).show();
}
public void getContent(String mRushId) {
mApiService = ApiClient.getClient().create(ApiInterface.class);
Call<List<Content>> call = mApiService.getRushContent(mRushId);
if(call!=null){
call.enqueue(new Callback<List<Content>>() {
#Override
public void onResponse(#NonNull Call<List<Content>> call, #NonNull Response<List<Content>> response) {
mContents = response.body();
if(mContents!=null && mContents.size()>0){
//noinspection ConstantConditions
List<LibraryCoverContent> coverContent = new ArrayList<>();
for(int i=0; i<mContents.size(); i++){
coverContent.add(new LibraryCoverContent
(mContents.get(i).getContent_id(), mContents.get(i).getRush_id(),
mContents.get(i).getContent(), mContents.get(i).getAttr(),
mContents.get(i).getDatetime(), mContents.get(i).getPage_no()));
}
mLibraryContentRepository.insertContentItems(coverContent);
}
}
#Override
public void onFailure(#NonNull Call<List<Content>> call, #NonNull Throwable t) {
// if(getActivity()!=null) Toast.makeText(getActivity(), "Network Error while downloading rush content", Toast.LENGTH_LONG).show();
}
});
}
}
#SuppressLint("StaticFieldLeak")
public void insertContentItems(final List<LibraryCoverContent> items) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
for(int i=0; i<items.size(); i++){
mLibraryCoverContentDao.insertCoverContents(items.get(i));
}
return null;
}
}.execute();
}

Does Google API Client need to be inside an Activity?

I am trying to design a good architecture for implementing Google API Services.
The current documentation looks like this:
public class MainActivity extends ActionBarActivity {
public static final String TAG = "BasicHistoryApi";
private static final int REQUEST_OAUTH = 1;
private static final String DATE_FORMAT = "yyyy.MM.dd HH:mm:ss";
/**
* Track whether an authorization activity is stacking over the current activity, i.e. when
* a known auth error is being resolved, such as showing the account chooser or presenting a
* consent dialog. This avoids common duplications as might happen on screen rotations, etc.
*/
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// This method sets up our custom logger, which will print all log messages to the device
// screen, as well as to adb logcat.
initializeLogging();
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
buildFitnessClient();
}
/**
* Build a {#link GoogleApiClient} that will authenticate the user and allow the application
* to connect to Fitness APIs. The scopes included should match the scopes your app needs
* (see documentation for details). Authentication will occasionally fail intentionally,
* and in those cases, there will be a known resolution, which the OnConnectionFailedListener()
* can address. Examples of this include the user never having signed in before, or
* having multiple accounts on the device and needing to specify which account to use, etc.
*/
private void buildFitnessClient() {
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs. What to do?
// Look at some data!!
new InsertAndVerifyDataTask().execute();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
MainActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(MainActivity.this,
REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG,
"Exception while starting resolution activity", e);
}
}
}
}
)
.build();
}
#Override
protected void onStart() {
super.onStart();
// Connect to the Fitness API
Log.i(TAG, "Connecting...");
mClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
.... // MORE CODE
}
This looks really ugly inside an Activity, what if I have multiple Activities using Google API Services.
Would it be possible to move everything to a Client.java class that just handles creation of a GoogleApiClient object.
How would I pass an activity context parameter to GoogleApiClient.Builder(this)? Should I use an event bus driven system that sends off context values from each activity to the client and builds it each time?
This is pretty ugly, any way I can trim this code so I don't have to copy it everywhere in like 30 activities?
How about a manager class GoogleApiManager.java that would handle all that for me? What sorts of interfaces would I need to implement on this?
Can I instead store inside an application class instead?
Would appreciate any help on this.
You are going to have to mess around with the code to get it all working correctly. I don't have google api client hooked up so I can't debug.
You could create a separate class like below
public class BuildFitnessClient {
private static boolean mAuthInProgress;
private static final String TAG = "BasicHistoryApi";
private static final int REQUEST_OAUTH = 1;
public static GoogleApiClient googleApiClient(final Activity activity, boolean authInProgress) {
mAuthInProgress = authInProgress;
return new GoogleApiClient.Builder(activity)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
mCallbacks.connected();
}
#Override
public void onConnectionSuspended(int i) {
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
activity, 0).show();
return;
}
if (!mAuthInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
mAuthInProgress = true;
result.startResolutionForResult(activity,
REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG,
"Exception while starting resolution activity", e);
}
}
}
}
)
.build();
}
/**
* Interface to communicate to the parent activity (MainActivity.java)
*/
private static MyCallbacks mCallbacks;
public interface MyCallbacks {
void connected();
}
public void onAttach(Activity activity) {
try {
mCallbacks = (MyCallbacks) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement Fragment One.");
}
}
}
Then in your Activity you could call it like:
public class TestingActivity extends AppCompatActivity implements BuildFitnessClient.MyCallbacks {
GoogleApiClient mClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testing);
new BuildFitnessClient().onAttach(this);
mClient = new BuildFitnessClient().googleApiClient(this, true);
}
#Override
protected void onStart() {
super.onStart();
mClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
#Override
public void connected() {
Log.e("Connected", "Connected");
new InsertAndVerifyDataTask().execute();
}
}

Categories