I am trying to add an RX observer to my SQLite database and I am surely missing something from my implementation as neither the onNext() and onCompleted() methods from my observer are not getting called.
Here is my observer:
private final Observer<List<Order>> mObjectiveObserver = new Observer<List<Order>>() {
#Override
public void onCompleted() {
System.out.println("Load completed");
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Order> objectives) {
System.out.println("On Next: " + objectives.size() + " elements found!");
orderAdapter.clear();
if (objectives != null) {
orderAdapter.addAll(objectives);
mCirclePulseView.setVisibility(View.INVISIBLE);
} else {
mCirclePulseView.setVisibility(View.VISIBLE);
}
orderAdapter.notifyDataSetChanged();
}
};
These are my LoaderCallback methods:
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
Loader<Cursor> loader = null;
switch (loaderId) {
case LOADER_ORDERS:
System.out.println("Create loader called");
loader = new CursorLoader(OrderManagerApplication.getAppContext(), OrderManagerContract.Order.CONTENT_URI,
QueryOrder.PROJECTION_SIMPLE, null, null, OrderManagerContract.Order.DATE_SORT);
break;
}
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (getActivity() == null) {
return;
}
if (data != null && !data.isClosed()) {
System.out.println("Finished loading orders, data not null");
switch (loader.getId()) {
case LOADER_ORDERS:
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
subscription = AndroidObservable
.bindFragment(this, DatabaseHelper.mainOrdersObservable(data))
.subscribeOn(Schedulers.computation())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(mObjectiveObserver);
System.out.println("I should be here, onLoadFinished");
break;
}
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
The content observer code follows below:
class HomeOrdersContentObserver extends ContentObserver {
private int mLoaderId = 0;
public HomeOrdersContentObserver(Handler handler, int loaderId) {
super(handler);
mLoaderId = loaderId;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
if (getActivity() == null) {
return;
}
Bundle bundle = new Bundle();
//bundle.putString(FILTER_TXT, lastFilterQ);
restartLoader(mLoaderId, bundle);
}
}
public void restartLoader(int loaderId, Bundle args) {
getLoaderManager().restartLoader(loaderId, args, this);
}
I have put logs everywhere in my code and they get printed as they should, except for this onNext and onCompleted methods. Any ideas what I might be missing from my implementation?
I see a few problems with the above code.
1st: you're not actually calling onNext/onCompleted anywhere. Since you're trying to connect the two paradigms (Loader & Rx), then you would need to put onNext in the onLoadFinished (with onError in case you want it called when there's no data, but that will close the subscription) and onCompleted in onLoaderReset
2nd: you're redoing the subscription in onLoadFinished, which I don't think you'd want - why would you want to resubscribe every time you have new data? You should do it when creating the loader, and unsubscribe when destroying the loader (onLoaderReset).
This is one possible implementation:
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
Loader<Cursor> loader = null;
switch (loaderId) {
case LOADER_ORDERS:
System.out.println("Create loader called");
loader = new CursorLoader(OrderManagerApplication.getAppContext(), OrderManagerContract.Order.CONTENT_URI,
QueryOrder.PROJECTION_SIMPLE, null, null, OrderManagerContract.Order.DATE_SORT);
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
subscription = AndroidObservable
.bindFragment(this, DatabaseHelper.mainOrdersObservable(data))
.subscribeOn(Schedulers.computation())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(mObjectiveObserver);
break;
}
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (getActivity() == null) {
return;
}
if (data != null && !data.isClosed()) {
System.out.println("Finished loading orders, data not null");
switch (loader.getId()) {
case LOADER_ORDERS:
mObjectiveObserver.onNext(data);
System.out.println("I should be here, onLoadFinished");
break;
}
} else {
mObjectiveObserver.onError("No data available");
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
Have in mind that structured like this, in case of no data it will actually close the subscription (onError does that) so you won't be able to receive any more data from the loader. If you don't want that, then in case of onError above, you would actually call onNext with null or new ArrayList and then take care of it in your observer.
Related
Note: I'm talking about a one-time purchase that removes ads from the app.
Google says you should call BillingClient.queryPurchases() in your onResume() and onCreate() but I'm not sure how to do that.
First off here's the code for the actual in-app purchase,
I just got it from a youtube tutorial and I don't even know if it's complete:
private void setupBillingClient(Context c) { //connect to google play
billingClient = BillingClient.newBuilder(c)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//The BillingClient is setup successfully
loadAllSkus();
}
}
#Override
public void onBillingServiceDisconnected() {
//TODO: implement retry logic to handle lost connections to Google Play by calling startConnection() again
}
});
}
private void loadAllSkus() {
if (billingClient.isReady()) { //first check if BillingClient is ready
final SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(#NonNull final BillingResult billingResult, #Nullable List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
assert skuDetailsList != null;
for (Object skuDetailsObject : skuDetailsList) {
final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
if (skuDetails.getSku().equals(sku)) { //if it found the sku in play store you can start billing flow
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); //use play store cache, no network
List<Purchase> purchases = result.getPurchasesList();
boolean isOwned = false;
if (purchases != null) //first check if he has already purchased
for (Purchase purchase : purchases) {
String thisSKU = purchase.getSku();
if (thisSKU.equals(sku)) {
isOwned = true;
Toast.makeText(getContext(), "You are a premium user", Toast.LENGTH_LONG).show();//TODO: Delete this toast
//TODO: Remove purchase options and ads here
break;
}
}
if (!isOwned) {
buyButton.setEnabled(true);
buyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build();
if (getActivity() != null)
billingClient.launchBillingFlow(getActivity(), billingFlowParams);
dismiss();
}
});
}
}
/* else if (skuDetails.getSku().equals("something else")) { //add other products
}*/
}
}
}
});
} else Toast.makeText(getContext(), R.string.billing_not_ready, Toast.LENGTH_LONG).show();
}
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult, #Nullable List<com.android.billingclient.api.Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase); //acknowledge the purchase
}
} else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
//already owned
} else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getSku().equals(sku) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(#NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Toast.makeText(getContext(), "Purchase Acknowledged", Toast.LENGTH_LONG).show();//TODO: Delete this maybe?
}
}
});
}
Toast.makeText(getContext(), R.string.purchase_done, Toast.LENGTH_LONG).show();
}
}
So apparently to call BillingClient.queryPurchases() I have to rewrite all of the above code in each and every activity?
The method BillingClient.queryPurchases() as you know returns a list of all the currently owned purchases items.
Replicating the same code again and again is always an incorrect approach / design.
What you need to do is to encapsulate your code and make it as independent as possible from any Activity or any other component, so it can be reused.
For a better approach check the official billing samples from Google. You will see that all the billing logic is encapsulated so it can be easily reused.
Java: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiJava
Kotlin: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiAppKotlin
Some clarifications, if you find that the samples are about subscriptions and you need non-consumables, or vice versa. The billing library is the same for both, the difference is only the data to be queried, for example for subscriptions use BillingClient.SkuType.SUBS and for non-consumables BillingClient.SkuType.INAPP. But the rest, how to connect with billing, how to acknowledge, initiate the purchase flow, etc., is the exactly same for both. There are no separate APIs or different way to implement it, you use the same API and the same implementation just changing what is being queried.
I think I found the answer. So thankful to everyone.
public void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult billingResult) {
Purchase.PurchasesResult purchasesResult=billingClient.queryPurchases(BillingClient.SkuType.SUBS);
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS,(billingResult1, list) -> {
Log.d("billingprocess","purchasesResult.getPurchasesList():"+purchasesResult.getPurchasesList());
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
!Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
//here you can pass the user to use the app because he has an active subscription
Toast.makeText(WebappActivity.this,"subbungSCCCCCriber",Toast.LENGTH_SHORT).show();
}
else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK &&
Objects.requireNonNull(purchasesResult.getPurchasesList()).isEmpty()) {
loadAllSKUs(); // toward the billingflow
}
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();
});
UPDATE(24/10/19) : I modified the language of the question a little bit. Downvoters, if its still unclear, please let me know publicly via comments, so I could refine it more (I just see a closing vote saying unclear, neither the mod name nor the reason). Please know that English is not my first language and I really want help on this.
So i guess this is more related to java fundamentals and OOP, but i have seen some patterns followed by usual Android classes. Google is often deprecating old functions in favour of better functions, and I am thinking of doing something similar with my custom View.
I want to build a video player in android that should be easy to create and should be returning a callback on several events. For that i want the user to use my functions instead of Built in video view's function. So i am applying several approaches to prevent user from using those built-in functions:
I am using the #Deprecated notation to show the function name with a strike , eg getVolume()
I have overriden those functions that i don't want user to use and replaced their implementation with errors like :
#Override #deprecated
public void getWidth(){
throw new UnsupportedOperationException("useMyVideoPlayer#getVideoWidth()");
}
I am implementing those functions by myself in init() or their respective alternatives. Here is the code( check the comment on the code line : super.setOnErrorListener(errorListener);)
public class MyVideoPlayer extends VideoView {
public static final String TAG = "MyVP>>";
private MyPlayerCurrentPlaybackState currentPlaybackState;
private MediaController mediaController;
#Nullable
private MyVideoPlayerListener playerListener;
public MyVideoPlayer(Context context) {
super(context);
init();
}
public MyVideoPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyVideoPlayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyVideoPlayer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void init() {
currentPlaybackState = MyPlayerCurrentPlaybackState.STOPPED;
mediaController = new MediaController(this.getContext());
mediaController.setAnchorView(this);
MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
Log.e(TAG, "internalListener:onCompletion: called");
mp.reset();
mp.setDisplay(MyVideoPlayer.this.getHolder());
currentPlaybackState = MyPlayerCurrentPlaybackState.STOPPED;
if (playerListener != null) {
playerListener.onCompleted(mp);
} else {
Log.e(TAG, "onCompletionListener:onCompletion: playerListener is null");
}
}
};
super.setOnCompletionListener(onCompletionListener);
MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
currentPlaybackState = MyPlayerCurrentPlaybackState.STOPPED;
if (playerListener != null) {
playerListener.onError(what, extra);
} else {
Log.e(TAG, "errorListener:onError: playerListener is null");
}
return true;// indicates we handled error
}
};
super.setOnErrorListener(errorListener);// <---setting error listener *inside* the view only and making setOnErrorListener(...) deprecated so that user won't use it
MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
//play(); // or should we call mp.start?
if (playerListener != null) {
playerListener.onPlayerPrepared(MyVideoPlayer.this);
} else {
Log.e(TAG, "preparedListener:onPrepared: player listener is null");
}
}
};
super.setOnPreparedListener(preparedListener);
}
#Nullable
public MyVideoPlayerListener getMyPlayerListener() {
return playerListener;
}
public void setMyPlayerListener(#NonNull MyVideoPlayerListener playerListener) {
this.playerListener = playerListener;
}
//---------following methods throw exception, do not use-----vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#Override #Deprecated
public void setOnCompletionListener(#Nullable MediaPlayer.OnCompletionListener implementorsListener) {
//update: user is now not adding the on completion listener at all. he/she will only use our methods now.
throw new UnsupportedOperationException("Use MyVideoPlayer#setMyPlayerListener(...) ");
}
#Override #Deprecated
public void setOnErrorListener(MediaPlayer.OnErrorListener implementorsListener) {
//update: user is now not adding the on completion listener at all. he/she will only use our methods now.
throw new UnsupportedOperationException("Use MyVideoPlayer#setMyPlayerListener(...) ");
}
#Override #Deprecated
public int getDuration() {
throw new UnsupportedOperationException("Use MyVideoPlayer#gettotalDuration(...) ");
}
#Deprecated
public void start() {
// did because it doesn't look cool
throw new UnsupportedOperationException("Use MyVideoPlayer#play() ");
}
#Override #Deprecated
public void stopPlayback() {
// did because it doesn't look cool
throw new UnsupportedOperationException("Use MyVideoPlayer#stop() ");
}
//----------------------------------------------------------------------------------------------
#Override #Deprecated
public void setOnPreparedListener(MediaPlayer.OnPreparedListener implementorsListener) {
throw new UnsupportedOperationException("use MyVideoPlayer#onPlayerPrepared()");
}
public void play() {
super.start();
if (playerListener != null) {
playerListener.onPlay();
} else {
Log.e(TAG, "play: player listener is null");
}
currentPlaybackState = MyPlayerCurrentPlaybackState.PLAYING;
}
#Override
public void pause() {
// didn't throwed any exception because its already cool
super.pause();
currentPlaybackState = MyPlayerCurrentPlaybackState.PAUSED;
if (playerListener != null) {
playerListener.onPause();
} else {
Log.e(TAG, "play: player listener is null");
}
}
#Override
public void resume() {
// didn't throwed any exception because its already cool
super.start();
if (playerListener != null) {
playerListener.onResume();
} else {
Log.e(TAG, "play: player listener is null");
}
currentPlaybackState = MyPlayerCurrentPlaybackState.PLAYING;
}
public void stop() {
if (currentPlaybackState != MyPlayerCurrentPlaybackState.STOPPED) {
super.stopPlayback();
if (playerListener != null) {
playerListener.onStop();
} else {
Log.e(TAG, "play: player listener is null");
}
currentPlaybackState = MyPlayerCurrentPlaybackState.STOPPED;
}
}
public int gettotalDuration() {
return currentPlaybackState ==
MyPlayerCurrentPlaybackState.STOPPED ? 0 : super.getDuration();
}
//returns current video volume in range 0-100
public int getVolume() {
// Get the system's audio service and get media volume from it.
AudioManager audioManager =
(AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
if (audioManager != null) {
double volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
double max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
if (max <= 0) {
return 0;
}
// Return a range from 0-100.
return (int) ((volume / max) * 100.0f);
}
return 0;
}
public enum MyPlayerCurrentPlaybackState {
STOPPED, PAUSED, PLAYING
}
public interface MyVideoPlayerListener {
void onPlay();
void onPause();
void onResume();
void onCompleted(MediaPlayer mp);
void onError( int what, int extra);
void onPlayerPrepared(MyVideoPlayer myVideoPlayer);
void onStop();
}
/* must implement features
public interface VideoPlayer {
void play();
void pause();
void resume();
int getCurrentPosition();
void seekTo(int videoPosition);
int getDuration();
int getVolume();
void stopPlayback();
void disablePlaybackControls();
void enablePlaybackControls();
void setVideoPath(String videoUrl);
void addPlayerCallback(PlayerCallback callback);
void removePlayerCallback(PlayerCallback callback);
}
*
* */
}
I hope the above example shows my intentions: i want user to NOT USE built in methods like start() , setOnErrorListener(...), etc , but the library would either handle those functions itself (and give a callback to user) or has defined some other functions that i want the user to use, instead of built in functions( for eg i want user to call custom views' play() function instead of already present start() function coming from the parent via inheritance.)
But when I call the code via these lines:
MyVideoPlayer mvp = findViewById(R.id.mvp_main);
mvp.setVideoURI(Uri.parse(MyTags.CONTENT_URL));
mvp.setMyPlayerListener(new MyVideoPlayer.MyVideoPlayerListener() {
#Override
public void onPlay() {
Log.e(TAG, "onPlay: Video is now playing" );
}
#Override
public void onPause() {
Log.e(TAG, "onPause: Video Paused" );
}
#Override
public void onResume() {
Log.e(TAG, "onResume: video resumed" );
}
#Override
public void onCompleted(MediaPlayer mp) {
Log.e(TAG, "onCompleted: video playback completed" );
}
#Override
public void onError(int what, int extra) {
Log.e(TAG, "onError: error happenned: what:"+what+",extra:"+extra );
}
#Override
public void onPlayerPrepared(MyVideoPlayer myVideoPlayer) {
Log.e(TAG, "onPlayerPrepared: video is prepared,plau video" );
myVideoPlayer.play();
}
#Override
public void onStop() {
Log.e(TAG, "onStop: media playback stopped" );
}
});
i.e when the user uses my library in their app, It would give an exception whens calling those library defined functions like mvp.play() saying java.lang.UnsupportedOperationException: Use MyVideoPlayer#play() , indicating my overridden start() function is being called instead of super.start() . Why? Also, am I using the #Deprecated annotation correctly (that is just for the sake of showing a strikethrough warning) or does this annotation make some unwanted changes as well?
you can apply proxy/decoration pattern:
write you own Videoplayer to implement and extend those classes that the original Vidoeview implements.
here is the a pseudo code which could give you a idea:
//extends and implements the same interface and parent as the VideoView did, o that they will have the same methods to work properly.
public class MyVideoViewer extends SurfaceView implements
MediaController.MediaPlayerControl{
//private modifier to prevent others from directly calling the system's player
private VideoView view;
//you can just delete start method, so users using your player cannot even see this method.
/**void start(){
}*/
public void init(){
// do your things. can necessary method from videoplayer
view.setOnCompletionListener(listener) etc.
}
public void play()
{
view.start();
}
//indirectly call all necessary methods to make sure the system's player work properly.
#override
public void draw(arg1,arg2){
view.draw(arg1,arg2);
}
}
by doing, your player only exposes the methods you want it to expose, hide all the unnecessary methods behind your users. still it can work properly, because underneath, there is a proper system Videoview working for your player.
#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();
}
I am new to Realm (and Android development) and I would like to use a Singleton class to simplify Realm data management so it's easier for my friends to use in our group project.
EpicPandaForce have written a singleton class called RealmManager here, but I couldn't find an example in implementing it, so this is what I have tried:
public class RealmManager {
private static RealmManager instance;
private final ThreadLocal<Realm> localRealm = new ThreadLocal<>();
RealmManager(){}
public synchronized static RealmManager getInstance(){
if(instance == null){
instance = new RealmManager();
}
return instance;
}
public Realm openLocalInstance() {
Realm realm = Realm.getDefaultInstance();
if(localRealm.get() == null) {
localRealm.set(realm);
}
return realm;
}
public Realm getLocalInstance() {
Realm realm = localRealm.get();
if(realm == null) {
throw new IllegalStateException("No open Realms were found on this thread.");
}
return realm;
}
public void closeLocalInstance() {
Realm realm = localRealm.get();
if(realm == null) {
throw new IllegalStateException(
"Cannot close a Realm that is not open.");
}
realm.close();
if(Realm.getLocalInstanceCount(Realm.getDefaultConfiguration()) <= 0) {
localRealm.set(null);
}
}
public void storePreferenceDao(int userID, String rank){
final PreferenceDao preferenceDao = new PreferenceDao();
preferenceDao.setUserID(userID);
preferenceDao.setRank(rank);
openLocalInstance();
getLocalInstance().executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(preferenceDao);
}
}, new Realm.Transaction.OnSuccess(){
#Override
public void onSuccess(){
System.out.println("Data is stored successfully!");
}
}, new Realm.Transaction.OnError(){
#Override
public void onError(Throwable error){
System.out.println("There is an error in storePreferenceDao()");
}
});
closeLocalInstance();
}
So when my friends want to store some data, they can just use:
RealmManager.getInstance().storePreferenceDao(123, "Alpaca");
Is this how it is supposed to be used or is it redundant? How can I make it more efficient?
Actually in this case, that method can still be called only from UI thread, and the local instance should be closed in the transaction callback (otherwise the onSuccess/onError won't be called)
You could make a method that is able to execute on bg thread if able, and on current thread if already on a bg thread
// method in RealmManager
public final void runTransaction(Realm.Transaction transaction) {
runTransaction(transaction, null, null);
}
public final void runTransaction(Realm.Transaction transaction, Realm.Transaction.OnSuccess onSuccess) {
runTransaction(transaction, onSuccess, null);
}
public final void runTransaction(Realm.Transaction transaction, Realm.Transaction.OnSuccess onSuccess, Realm.Transaction.OnError onError) {
Realm realm = openLocalInstance();
if(realm.isAutoRefresh()) {
realm.executeTransactionAsync(transaction, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
try {
if(onSuccess != null) {
onSuccess.onSuccess();
}
} finally {
closeLocalInstance();
}
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable e) {
try {
if(onError != null) {
onError.onError(e);
}
} finally {
closeLocalInstance();
}
}
});
} else {
try {
realm.executeTransaction(transaction);
if(onSuccess != null) {
onSuccess.onSuccess();
}
} catch(Exception e) {
if(onError != null) {
onError.onError(e);
}
throw new RuntimeException(e);
} finally {
closeLocalInstance();
}
}
}
If you add this method, then you can now execute a transaction that will either be executed on background thread via async transaction method if possible, using synchronous transaction method if not on a looper thread (f.ex. background thread)
This way you can now do
public void storePreferenceDao(int userID, String rank) {
final PreferenceDao preferenceDao = new PreferenceDao();
preferenceDao.setUserID(userID);
preferenceDao.setRank(rank);
runTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(preferenceDao);
}
}, new Realm.Transaction.OnSuccess(){
#Override
public void onSuccess(){
System.out.println("Data is stored successfully!");
}
}, new Realm.Transaction.OnError(){
#Override
public void onError(Throwable error){
System.out.println("There is an error in storePreferenceDao()");
}
});
}
Or just
public void storePreferenceDao(int userID, String rank) {
final PreferenceDao preferenceDao = new PreferenceDao();
preferenceDao.setUserID(userID);
preferenceDao.setRank(rank);
runInTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(preferenceDao);
}
});
}
You know, I always felt I should add a runTransaction() method to that example. Whether it should default to using executeTransactionAsync by default if able or not is up for debate, though.
A simple Singleton example,
public class MySingleton {
private static MySingleton sMySingleton;
//private constructor.
private MySingleton() {
if (sMySingleton != null){
throw new RuntimeException("Use getInstance() for the instance");
}
}
public synchronized static MySingleton getInstance() {
if (sMySingleton == null){
sMySingleton = new MySingleton();
}
return sMySingleton;
}
}
Hope it helps!
This can be not the best answer but here how i am using realm in my applications
public class BaseActivity extends AppCompatActivity {
public Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
realm = Realm.getDefaultInstance();
}
}
Extend BaseActivity in other Activities according to your use
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Here you can directly access realm object and perform your task
realm.where()//just example
}
#Override
protected void onDestroy() {
super.onDestroy();
if(realm!=null)
realm.close();
//Don't forget to close realm object
}
}
I am not saying this is the best way but it one the best way. using this way i can easily manage my realm class and can reduce errors related to realm.For fragments You can make BaseFragment and extend it in other fragments.
Hope this will help you...Let me know if you get any other better way for this