I am querying POJO which is NOT being Observed / Non-Live data from an IntentService that was started in a PreferenceFragment. However a second my application crashes and log displays:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:204)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:232)
at vault.dao.xxxDao_Impl.getAllNonLivePojoItems(xxxDao_Impl.java:231)
I want to know why is my program throwing this exception. as per https://stackoverflow.com/a/23935791/8623507
my database query[s] are inside an IntentService that runs In its own thread so i should be in the green. here is my code:
Inside IntentService
--------------------
// ERROR OCCURS HERE
List<POJO> pojoList = localRepo.getAllNonLivePojoItems(); // <= ERROR POINTS HERE
if (pojoList != null && pojoList.size() > 0) {
for (Pojo pojo : pojoList ){
// Do Long Running Task Here ....
}
Also I instantiate The Objects Being Used and call the above methods from those Objects Throughout the IntentService in OnHandleIntent like so:
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
LocalRepo localRepo = new LocalRepo(this.getApplication());
PojoHelper pojoHelper = new PojoHelper(this, localRepo);
if (LOGOUT.equals(action) && type != null) {
Log.d(TAG, "onHandleIntent: LOGOUT");
pojoHelper.logoutPojo();
}
else if(DELETE.equals(action) && type != null){
Log.d(TAG, "onHandleIntent: DELETE_POJO");
pojoHelper.deletePojo(true);
}
}
}
I assume you get callback from AsyncTask onPostExecute() method which runs on UI thread. It is prohibited to use database or network calls inside UI thread because it can block UI.
Execute your code where you access database inside new thread.
Example:
Executors.newSingleThreadExecutor().execute(()->{
//TODO access Database
});
One thing i failed to mention was that the method was being executed within an async's response callback method
PojoWarehouse.processPojoItems(new AsyncPojoCallback() {
#Override
public void done(Exception e) {
if (e == null) {
// ERROR OCCURS HERE
List<POJO> pojoList = localRepo.getAllNonLivePojoItems(); // <= ERROR POINTS HERE
if (pojoList != null && pojoList.size() > 0) {
for (Pojo pojo : pojoList ){
// Do Long Running Task Here ....
}
} else
Log.d(TAG, "done: Error Logging Out: " + e.getLocalizedMessage());
}
});
I cannot explain on a technical level why this fixed the issue, however suggestions are welcomed.
Related
I have been struggling on this question a few days. So what I want to do on Android Slices is create slice with information which is from the back-end service. For example:
on SliceProvider class
#Override
public Slice onBindSlice(Uri sliceUri) {
l.d(TAG,"onBindSlice called");
switch (sliceUri.getPath()) {
case "/product":
makeRequestForProduct();
return createProductSlice(sliceUri);
}
return null;
}
and
private void makeRequestForProduct() {
String url = Environment.getInstance().getCompleteUrl("etc..");
RetrofitFactory.defaultBuilder(ProductWebInterface.class)
.getProductResponse(url).enqueue(new ProductWebcallback());
}
public void onEventMainThread(ProductReceivedEvent response) {
if (response.getProduct() != null) { //do something
}
}
But I have no idea how to do it. Above code is not working. It is giving me an Exception.
According to Google Documentation here :
onBindSlice should return as quickly as possible so that the UI tied to this slice can be responsive. No network or other IO will be allowed during onBindSlice. Any loading that needs to be done should happen in the background with a call to ContentResolver.notifyChange(Uri, ContentObserver) when the app is ready to provide the complete data in onBindSlice.
You must therefore do your work in the background thread.
See an example below in Kotlin:
private fun makeRequestForProductInTheBackground(sliceUri : SliceUri) {
Completable.fromAction {
makeRequestForProduct(sliceUri)
}.subscribeOn(Schedulers.io()).subscribe()
}
After the request completes you can save your data somewhere e.g a variable or a repository.
fun onEventMainThread(response: ProductReceivedEvent) {
if (response.getProduct() != null) {
//Save your data in a variable or something depending on your needs
product == response.getProduct()
//This will call the *onBindSlice()* method again
context?.contentResolver?.notifyChange(sliceUri, null)
}
}
You can then use the product data in your createProductSlice(sliceUri) method
An async task in a Fragment used to crash my app if the app was suddenly closed. The post execute code ran (e.g. show a Toast), but there was no app anymore.
I thought I fixed this by checking getContext() != null before running post execute code, but I got another crash.
java.lang.IllegalStateException:
at android.support.v4.app.Fragment.requireContext (Fragment.java:614)
at android.support.v4.app.Fragment.getResources (Fragment.java:678)
at android.support.v4.app.Fragment.getString (Fragment.java:700)
at com.grammarbud.android.grammarbud.MainFragment$2.onBillingServiceDisconnected (MainFragment.java:310)
I read that
Fragments now have requireContext(), requireActivity(), requireHost(), and requireFragmentManager() methods, which return a NonNull object of the equivalent get methods or throw an IllegalStateException.
https://developer.android.com/topic/libraries/support-library/revisions.html#27-1-0
Does this mean getContext() doesn't return null anymore? But then what does the below excerpt mean? Will it return null or not when not attached to the Activity? I don't understand the language.
The getActivity and getContext methods return nullable types because when the Fragment is not attached to an Activity, these methods already returned null. There's no change in behaviour, it's just explicitly marked now, so you can safely handle it.
https://stackoverflow.com/a/47253335/3268303
My code, which is run in a Fragment
private void connectToPlayStore() {
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponseCode) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
if (getContext() != null) {
queryProductDetails();
}
}
}
#Override
public void onBillingServiceDisconnected() {
// Line below ***CRASHES*** app if closed prematurely
SharedHelper.showToast(getContext(), getString(R.string.no_connection_to_play_store));
}
}
});
}
The SharedHelper function to show the Toast, which would catch if context is null, but execution doesn't get this far it seems to me
public static void showToast(Context context, String message) {
if (context != null) {
Toast newToast = Toast.makeText(context, message, Toast.LENGTH_LONG);
showToast(newToast);
}
}
So how to properly provide for the scenario when an async task is running in a fragment and the app is suddenly closed? Should I try to end connection with BillingClient.endConnection()? Will this guarantee that post execute code is not run? It doesn't explicitly say in the docs.
Also someone mentioned isAdded(). Should I check isAdded() instead of getActivity() != null && getContext() != null?
I have a simple Android app. The main activity looks for a file on the internal device storage. What happens after that depends on whether the file exists or not. This sequence works just fine as the whole execution is synchronous.
If I now use Google Drive API, all file access are asynchronous and the main activity goes on without waiting for the result of my search...
I found no way to 'force' a synchronous behavior. Synchronous calls of Google API are not allowed in the UI thread.
Moreover, performing such calls in an AsyncTask leads to the same problem...
Any idea on how to handle such scenarios ?
Regards,
Laurent
------------------------- EDIT ----------------------------------
I tried several options but they all lead to the same result.
Both the main activity and the asynctask used to look for my file get blocked.
Any idea why and what to do ?
Main activity code :
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed) {
Log.i(mTag, "Terms of use agreement needs to be checked");
mCountDownLatch = new CountDownLatch(1);
TermsOfUseAgreementSearchTask searchAgreementTask = new TermsOfUseAgreementHandler().new TermsOfUseAgreementSearchTask();
// Start the async task to look for agreement file using parallel execution
searchAgreementTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
// Block the main thread so that the task gets a chance to perform its Google Drive access...
mCountDownLatch.await(); // MAIN ACTIVITY HANGS HERE !
} catch (InterruptedException e) {
Log.e(mTag, "Failed to look for user agreement file", e);
}
// The async task should have finished (and decreased mCountDownLatch so that await returns)
// At this moment, the result should be available for the main activity in mTermsOfUseAgreed...
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed)
TermsOfUseAgreementHandler.checkTermsOfUse(this);
}
AsyncTask code :
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(MainActivity.mGoogleApiClient).queryChildren(MainActivity.mGoogleApiClient, lookForAgreement).await().getMetadataBuffer(); // ASYNCTASK HANGS HERE !
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
}
------------------------- EDIT 2 ----------------------------------
I also tried to move the connection to the drive API from the main activity to the asynctask as shown in a drive API example link.
I still get the same result but the problem is now the connection because the associated callback listener is never called...
public class TermsOfUseAgreementSearchTask extends AsyncTask<Void, Void, Boolean> {
private GoogleApiClient mClient = null;
final private CountDownLatch latch = new CountDownLatch(1);
public TermsOfUseAgreementSearchTask(Context context) {
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER);
mClient = builder.build();
mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
Log.i(MainActivity.mTag, "Connected");
latch.countDown();
}
#Override
public void onConnectionSuspended(int arg0) {}
});
mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i(MainActivity.mTag, "Connection failed");
latch.countDown();
}
});
}
#Override
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Connect to drive API");
mClient.connect();
try {
Log.i(MainActivity.mTag, "Wait for connection");
latch.await(); // ASYNC TASK HANGS HERE !
} catch (InterruptedException e) {
return false;
}
if (!mClient.isConnected()) {
return false;
}
try {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(mClient).queryChildren(mClient, lookForAgreement).await().getMetadataBuffer();
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
//MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
} finally {
mClient.disconnect();
}
}
}
I need to solve putting data to a realm database like this:
I have an object called obtained_code;
I have a realmList of Obtained codes in an object called Offer;
I download obtained codes separately, and by their offer id assign them to the lists of each object. The problem is that I can't add them because when I check the size, it's always 0.
Here is the code:
ObtainedCodes codes = response.body();
for (ObtainedCode c : codes.getObtainedCodes()) {
Offer offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
if (offer != null) {
Log.d("Size", "Offer not null");
realm1.beginTransaction();
RealmList<ObtainedCode> list = offer.getObtained_codes();
if (!list) { // if the 'list' is managed, all items in it is also managed
RealmList<ObtainedCode> managedImageList = new RealmList<>();
for (ObtainedCode item : list) {
if (item) {
managedImageList.add(item);
} else {
managedImageList.add(realm1.copyToRealm(item));
}
}
list = managedImageList;
}
offer.setObtained_codes(obtainedCodes);
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
realm1.copyToRealmOrUpdate(offer);
realm1.commitTransaction();
}
offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
}
1.) the Ravi Tamada tutorial on InfoHive is a terrible mess, please refer to my remake of that example instead.
If you managed to start using 0.82.1 because Ravi Tamada claimed that a 4 years old version is "stable", well I know that it's not. Use 1.2.0 instead (or the latest version which is 3.4.1)
And if you see a RealmController.with(), run, because it ignores thread-confinement. The moment you try to access it from a background thread, it'll crash.
On background threads, you'd need to do
#Override
public void run() {
try(Realm realm = Realm.getDefaultInstance()) {
repository.whatever(realm); // pass Realm instance to database methods
} // auto-close
// end of thread
}
2.) you are executing writes on the UI thread, that is bad, from UI thread you should use realm.executeTransactionAsync(), but in your case you should actually execute the Retrofit call on a background thread using Ęxecutors.newSingleThreadedPool() and call it with call.execute() instead of call.enqueue().
3.) You should write to Realm on the background thread, and on the UI thread you should use RealmChangeListener to listen to writes.
4.) your code doesn't work because you're setting an unmanaged list to a managed RealmObject.
You should modify the existing RealmList inside the RealmObject, and add only managed objects to it.
Executor executor = Executors.newSingleThreadExecutor(); // field variable
// ...
void someMethod() {
executor.execute(new Runnable() {
#Override
public void run() {
Response<ObtainedCodes> response = retrofitService.getObtainedCodes().execute(); // run on current thread
ObtainedCodes codes = response.body();
if(codes == null) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for(ObtainedCode obtainedCode : codes.getObtainedCodes()) {
Offer offer = realmRepository.getOffer(realm, obtainedCode.getOfferId());
if(offer == null) {
offer = realm.createObject(Offer.class, obtainedCode.getOfferId());
// map properties to offer if possible
}
RealmList<ObtainedCode> offerCodes = offer.getObtainedCodes();
ObtainedCode managedObtainedCode = realm.where(ObtainedCode.class).equalTo("obtainedCodeId", obtainedCode.getId()).findFirst();
if(managedObtainedCode == null) {
managedObtainedCode = realm.createObject(ObtainedCode.class, obtainedCode.getId());
// map properties from obtained code to managed obtained code
}
if(!offerCodes.contains(managedObtainedCode)) {
offerCodes.add(managedObtainedCode);
}
}
}
});
}
}
});
}
In my app, I am synchronizing all my data via realm transactions in the background thread. However, occasionally when I try to retrieve a recently synchronized object in the UI thread, I get a null pointer exception. I have written a routine that seems to be a fix to this small latency problem, but it feels like a "hacky" solution. Any guidance would be greatly appreciated. I have posted a simplified version of the routine below.
private void attemptToEnterActivity(){
// Retrieve the object's key.
String key = Application.getInstance().getRecentlySyncedPrimaryKey();
// Retrieve the realm object with the key.
Realm realm = Realm.getDefaultInstance();
Object object = realm.where(Object.class)
.equalTo("key", key)
.findFirst();
if (object != null) {
new NavigationController(this).activity_start(new Intent(this, Activity.class));
} else {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
attemptToEnterActivity();
}
}, 200);
}
}
Instead of guessing when the object will be fetched and stored, the suggestion is to use RealmChangeListener. See doc here for more information.
In your case, you can also use Realm's async query like:
RealmResults<Object> results = realm.where(Object.class)
.equalTo("key", key).findAllAsync();
results.addChnageListener(newRealmChangeListener<RealmResults<Object>>() {
#Override
public void onChange(RealmResults<Object> objects) {
if (objects.size() > 0) {
Object object = objects.first();
// ....
}
}
});