How to correctly use Realm - android

I was trying to avoid creating and managing Realm objects, in my android application, for every fragment. I am thinking ThreadLocalVariable might be a good start.
public class RealmInstanceGenerator extends ThreadLocal<Realm> {
public Realm getRealmForMyThread(Context context) {
if(get() == null && context != null)
super.set(Realm.getInstance(context));
return get();
}
public void setRealmForCurrentThread(Context context) {
if(context != null)
super.set(Realm.getInstance(context));
}
#Override
protected Realm initialValue() {
return null;
}
#Override
public void remove() {
if(get() != null) get().close();
super.remove();
}
}
I would just create a static final object of RealmInstanceGenerator in my utils singleton class and call setRealmForCurrentThread in my MainActivity. Then I will call remove when my activity dies. For any new thread a new Realm object will be automatically generated.
Is it a good strategy ?

Christian from Realm here.
It is a good strategy, and luckily we already implemented it for you :) All Realm instances are already being cached in a ThreadLocal and we keep track of instances using a counter. The Realm is only fully closed once the counter reaches 0.
This means as long as you always calls close() (which you should), it is effectively the same as your remove() method.
You can see the pattern used in this example here: https://github.com/realm/realm-java/tree/master/examples/threadExample/src/main/java/io/realm/examples/threads
And the source code for the Realm class is here: https://github.com/realm/realm-java/blob/master/realm/src/main/java/io/realm/Realm.java

Related

What process is the best way to send on API and save SQLiteDatabase?

I'm new on Android and working an big app which has sending data to API and saving it on SQlite. All of this process is on one class file . But it leaves me on an error. Sometimes the device hanged. other scenario is the data is incomplete . I have read about Intent Service and Services and I want to learn about the two, but I'm wondering how to get all of my data from UI and put it on services. May I know How?
It depends on the nature of the application. If this should happen in response to a user input...you could well use an AsyncTask. Otherwise, a background service could also do the job.
What you should NEVER do is run a network operation and/or database access on the main UI thread.
Services can receive data via intents. The way to send these intents depend on the type of service (Started, Bound or both). There are plenty of resources out there you can read...here's one from Android documentation...
https://developer.android.com/guide/components/services
An Example of an AsyncTask
The example below shows an implementation of AsyncTask that fetches a user's details from a network resource...
public class FetchUserTask extends AsyncTask<String,Void, UserDTO> {
private FetchUserTaskListener listener;
#Override
protected UserDTO doInBackground(String...params){
if(params == null || params.length == 0)
return null;
String userID = params[0];
UserDataProvider provider = new UserDataProvider(userID);
try {
return provider.get(userID);
}
catch(Exception ex){
//log the error
return null;
}
}
#Override
protected void onPostExecute(UserDTO user){
if(listener != null)
listener.onCompleted(user);
}
public void setListener(FetchUserTaskListener listener){
this.listener = listener;
}
public interface FetchUserTaskListener{
void onCompleted(boolean success);
}
}
How'd you use this AsyncTask?
For example, in an Activity, you would use it as below...
public class UserDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//instantiate activity...
super.onCreate(savedInstanceState);
setContentView(R.layout.whatever_layout);
fetchUser(userId);
}
private void fetchUser(String userID){
FetchUserTask task = new FetchUserTask();
task.setListener(new FetchUserTaskListener<UserDTO>() {
#Override
public void onCompleted(UserDTO user) {
//CAUTION: make sure the activity hasn't been stopped before
//accessing any UI elements and/or context
}
}
task.execute(userID);
}
}
Note
You can (and will need to) make the example(s) above a bit more sophisticated. For example you can have the FetchUserTaskListener's onCompleted method return also an error message if an error occurred.
You will also need to check whether the activity has been paused or stopped before you access any context-bound data otherwise you might get an ILlegalStateException.
Make use of SQLiteOpenHelper class and it has methods to be overridden in your own class by extending SQLiteOpenHelper. Create Add, Update, Delete, Get methods as per your requirement in this class and keep this class as Singleton pattern. User Asynctasks to call those methids and you are done.
Hope that helps you visualise things in better way.

What happens if I close my android application without closing an open realm instance?

I have kept a single realm instance opened on main thread in Application class and I use that single instance to do all kinds of DB operations from MainActivity. Since my application has a single activity, I close the instance in the activity's onDestroy(). The app is working fine for me as of now.
What are the repercussions of not doing a realm.close()? My database hasn't corrupted with or without the same.
Also, I've read that there are scenarios in which the Activity's onDestroy() may not get called at all. What effects the database can have in such a scenario if closing realm is so important?
public class MyApp extends Application {
private static MyApp instance;
private Realm realm;
public void onCreate() {
super.onCreate();
Realm.init(this);
Realm.setDefaultConfiguration(new RealmConfiguration.Builder()
.schemaVersion(BuildConfig.VERSION_CODE)
.migration(new RealmMigrationClass())
.compactOnLaunch()
.build());
realm = Realm.getInstance(Realm.getDefaultConfiguration());
}
public static MyApp getInstance() {
return instance;
}
public Realm getRealm() {
return realm;
}
}
MainActivity
public class MainActivity extends Activity {
#Override
protected void onDestroy() {
MyApp.getInstance().getRealm().close();
super.onDestroy();
}
}
Closing the realm instance is very important because of realm core has been written in c++ programming language and is compiled in the native code.And we know the c++ garbage collection does not run automatically we require to manually call the garbage collection.So when you call the realm.close() it means that realm deallocation the native memory means free or delete the pointer variable and also do the file descriptor job.From realm.close() means you give the command or tell to native c++ compiler to run the garbage collection.
If you look the "doc" (REALM_DOC) for Realm for Java you can find:
Realm implements Closeable to take care of native memory deallocation
and file descriptors, so always close your Realm instances when you’re
done with them.
Realm instances are reference counted—if you call getInstance twice in
a thread, you need to call close twice as well. This allows you to
implement Runnable classes without having to worry about which thread
will execute them: simply start it with getInstance and end it with
close.
Personally I suggest you to define a class in which define your Realm functions and an "Realm attribute" (like a "RealmHelper" class) then inside this class define:
- a unstatic Realm
- a static RealmHelper instance
You will always use this RealmHelper static instance for all operations in your Realm inside your main Thread, inside other threads you will call "new RealmHelper()" and CLOSE the realm just after you did the operation.
Doing this in your MainThread you just need to close ONE realm instance when the application get closed, to do this you can use the "Application.ActivityLifecycleCallbacks" interface inside a Custom defined Application class (so which extends Application of Android).
Example inside you Application custom class:
/* START Override ActivityLifecycleCallbacks Methods */
#Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
#Override
public void onActivityStarted(Activity activity) {
// Check if your MyRealmClass instance is null or is closed, in this case
// re-initialize it.
if(MyRealmClass.getInstance() == null || MyRealmClass.getInstance().getRealm().isClosed()){
MyRealmClass.initInstance();
}
}
#Override
public void onActivityResumed(Activity activity) {
}
#Override
public void onActivityPaused(Activity activity) {
}
#Override
public void onActivityStopped(Activity activity) {
if(!AppUtils.isAppOnForeground(this)){
// Close your MyRealmClass instance
if(MyRealmClass.getInstance() != null) {
MyRealmClass.getInstance().close();
MyRealmClass.getInstance().logRealmInstanceCount(LABEL_APP_IN_BACKGROUND);
MyRealmClass.setMyInstance(null);
}
}
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
#Override
public void onActivityDestroyed(Activity activity) {
}
/* END Override ActivityLifecycleCallbacks Methods */
Code of "isAppOnForeground" (check if your app is in foreground, if is not this mean your app is being closed):
public static boolean isAppOnForeground(Context context) {
boolean ret = false;
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if(appProcesses != null){
String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
ret = true;
}
}
}
return ret;
}
Your "MyRealmClass" will look like:
public class MyRealmClass {
protected Realm mRealm;
protected static MyRealmClass mInstance;
public MyRealmClass() {
mRealm = Realm.getDefaultInstance();
}
public static MyRealmClass initInstance(){
if(mInstance == null){
mInstance = new MyRealmClass();
}
return mInstance;
}
public static MyRealmClass getInstance(){
return mInstance;
}
public static void setMyInstance(MyRealmClass instance) {
mInstance = instance;
}
public Realm getRealm() {
return mRealm;
}
public void setRealm(Realm realm){
this.mRealm = realm;
}
public void close() {
if (mRealm != null) {
try {
mRealm.close();
} catch(Exception e){
onException(e);
}
}
}
[...]
Then you need to check that all your Realm instance is not closed when you use a RealmObject or you do some operation in your Realm. And if it is closed (because the app got in background and then restarted) you need to re-initialize the realm (if you have an activity with a MyRealmClass instance as attribute).
Example in a BaseMyActivity:
public abstract class MyBaseActivity extends AppCompatActivity {
protected MyRealmClass mRealmClass;
/* START Override Lifecycle Methods */
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initMyRealmClass();
Lyra.instance().restoreState(this, savedInstanceState);
}
#Override
protected void onStart() {
super.onStart();
initMyRealmClass();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Lyra.instance().saveState(this, outState);
}
/* END Override Lifecycle Methods */
/* START Private Methods */
protected void initMyRealmClass(){
if(mRealmClass == null || mRealmClass.getRealm().isClosed()){
mRealmClass = MyRealmClass.initInstance();
}
}
/* END Private Methods */
}
Basically all your activities will extend this BaseActivity if they need to use Realm functions. (Lyra is used to save the state of any of your attributes: LYRA)
REMEMBER THAT:
if you set or get some attributes from a RealmObject or you get an object from a RealmList or RealmResults you NEED THAT YOUR REALM INSTANCE, from which the object was take, IS OPEN.
OTHERWISE you need to use this method when you init a variable with objects from the realm: (this methods should be placed in yuour "MyRealmClass")
public <T extends RealmObject> List<T> toList(RealmResults<T> results) {
return mRealm.copyFromRealm(results);
}
public <T extends RealmObject> List<T> toList(RealmList<T> results) {
return mRealm.copyFromRealm(results);
}
public <T extends RealmObject> T copyObjectFromRealm(T obj) {
return mRealm.copyFromRealm(obj);
}
public <T extends RealmObject> RealmResults<T> findAllObject(Class<T> classObject) {
RealmQuery<T> query = mRealm.where(classObject);
return query.findAll();
}
Now if you need to get a List of "MyRealmObjectClass" objects and add them to an adapter you will do this:
List<MyRealmObjectClass> myObjects = mRealmClass.toList(mRealmClass.findAllObject(MyRealmObjectClass.class))
myAdapter.addAll(myObjects);
Doing this if you "get" or "set" an attribute after the Realm instance, from which you got the objects, was closed (for example after the app get to background and then restarted) you won't get an exception.
BUT if you "set" an attribute of your RealmObject this WON'T BE SET in the REALM INSTANCE, so to change the value of a RealmObject inside the Realm in this case you need to Save the object!
OTHERWISE if you have a RealmResults or a RealmObject which is still connected to the Realm, so you can directly change, inside a transaction, an attribute of it and it will be changed inside the Realm too.
To do a Realm Transaction I suggest you to follow the DOC in the first link and, if you don't need to close the Realm in the Finally block, enable lambda and do this:
mRealm.executeTransaction(
realm -> {
[do your Realm operations]
}
)
or you can also do:
public boolean doRealmOperation(Object... params){
AtomicBoolean ret = new AtomicBoolean(false);
mRealm.executeTransaction(
realm -> {
try{
[do your realm operation]
ret.set(true);
} catch(Exception e){
onException(e)
ret.set(false);
}
}
)
}
in this case you need to use the "AtomicBoolean" because you will set the value you want to return inside the transaction, but inside a transaction the value got from outside of the transaction itself (in this case the "ret" variable) MUST BE A FINAL variable. But you can't define "ret" as "final" and then set it again, so you need to use the "AtomicBoolean" to set the variable outside the transaction and set it again inside the transaction itself.
(You can also avoid this problem by using a temporary variable to get the "true/false" value inside the transaction and then set the "ret" variable using that "temp variable". But personally I prefer to use "AtomicBoolean" class which is, I think, safer and more clean than a temp variable)
Hope this is helpful,
see you by and happy coding! ;)
Realm implements Closeable to take care of native memory deallocation and file descriptors, so always close your Realm instances when you’re done with them.
Realm instances are reference counted—if you call getInstance twice in a thread, you need to call close twice as well.
From my personal experience not closing realm has not caused a lot of issues, in fact when I tried closing it at times it would cause an issue when the app went into the background and was then resumed which caused a crash due to realm instance being closed, I am not sure why a new instance of realm was not created in that case, might have been a bug.
As of now I follow the realm docs and close my realm instances until they cause an issue.
General coding practises suggest that anything that is opened should be safely closed.
Yes, it will get closed only if you called close() method on your application's destroy() method. Remember Realm implements Closeable in order to take care of native memory deallocation and file descriptors so it is important to close your Realm instances when you are done with them.
For further info visit this link.

Global Realm Instance in Android

(I've seen other posts on this but they're a bit outdated, mostly referencing realm 1.x)
Been looking for the best way to institute realm in our app. We're currently all over the place and looking to centralize our realm instance.
In this link (https://medium.com/#Zhuinden/basics-of-realm-a-guide-to-using-realm-1-2-0-634471c0fe8f), I found what I think is a great global implementation but I have 2 questions on it:
1. Does this still apply to current versions of realm (currently at 4.1)?
2. What's the purpose of the retained fragment and can it be done without one?
Thanks in advance!
Here's the relevant section from the link above:
- Making a global Realm instance
I set up a retained fragment in my activity which increments the current active > Activity count. Open the
Realm if the counter went from 0 to 1. Close the Realm if the counter
went from 1 to 0.
import android.content.Context;
import android.util.Log;
import io.realm.Realm;
import io.realm.RealmConfiguration;
/**
* Created by Zhuinden on 2016.08.16..
*/
public class RealmManager {
static Realm realm;
static RealmConfiguration realmConfiguration;
public static void initializeRealmConfig(Context appContext) {
if(realmConfiguration == null) {
setRealmConfiguration(new RealmConfiguration.Builder(appContext)
.deleteRealmIfMigrationNeeded()
.build());
}
}
public static void setRealmConfiguration(RealmConfiguration realmConfiguration) {
RealmManager.realmConfiguration = realmConfiguration;
Realm.setDefaultConfiguration(realmConfiguration);
}
private static int activityCount = 0;
public static Realm getRealm() {
return realm;
}
public static void incrementCount() {
if(activityCount == 0) {
if(realm != null) {
if(!realm.isClosed()) {
realm.close();
}
}
realm = Realm.getDefaultInstance();
}
activityCount++;
}
public static void decrementCount() {
activityCount--;
if(activityCount <= 0) {
activityCount = 0;
realm.close();
Realm.compactRealm(realmConfiguration);
realm = null;
}
}
}
The setRealmConfiguration() method is so that you can replace the
default configuration from instrumentation tests with a config for an
inMemory() Realm. If you use this, then you can easily count activity
references using a retained fragment!
public class BooksScopeListener extends Fragment { // could go to base class
public BooksScopeListener() {
setRetainInstance(true);
RealmManager.incrementCount();
}
#Override
public void onDestroy() {
RealmManager.decrementCount();
super.onDestroy();
}
}
public class BooksActivity extends AppCompatActivity {
Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
RealmManager.initializeRealmConfig(getApplicationContext()); // could go to base class
super.onCreate(savedInstanceState);
BooksScopeListener fragment = (BooksScopeListener) getSupportFragmentManager().findFragmentByTag("SCOPE_LISTENER"); // could go to base class
if(fragment == null) {
fragment = new BooksScopeListener();
getSupportFragmentManager().beginTransaction().add(fragment, "SCOPE_LISTENER").commit();
}
//get realm instance
realm = RealmManager.getRealm();
Ta-dah, now you can freely access RealmManager.getRealm() anywhere for
the UI thread. Or just #Inject this from a module. (Don’t forget that
the scoped approach per activity or fragment works too, I just prefer
this one because I used #Singleton component over #Subcomponents).
I wrote that article about a year ago, and while that creates a global Realm for the UI thread, you won't be able to just use it on any thread. And if you open/close the Realm on background threads, you still need to pass Realm as method argument.
Does this still apply to current versions of realm (currently at 4.1)?
Realm lifecycle management is still a thing that you need to do, and the same principle would apply even for 4.1.0, just like it did in 1.2.0. This is one thing that never really changed :)
What's the purpose of the retained fragment and can it be done without one?
The retained fragment is added so that you don't close/reopen the Realm just because you rotated the screen; lately you can do this with ViewModel's constructor + onCleared() method as well.
I just found that retained fragments are the trustworthiest way of listening to the lifecycle without configuration change messing with the lifecycle callbacks.
The RealmManager shown in the article is responsible solely for the automatic lifecycle management on the UI thread. If used on the UI thread, it still works fine. In fact, that's still where I'd call open/close from in the following example, for the UI thread, anyways.
If you want a singleton manager class for Realms across threads, a starting out point would be to use following class (a bit proof of concept as I haven't used it in production yet, but hey):
public class RealmManager {
private final ThreadLocal<Realm> localRealms = new ThreadLocal<>();
/**
* Opens a reference-counted local Realm instance.
*
* #return the open Realm instance
*/
public Realm open() {
checkDefaultConfiguration();
Realm realm = Realm.getDefaultInstance();
if(localRealms.get() == null) {
localRealms.set(realm);
}
return realm;
}
/**
* Returns the local Realm instance without adding to the reference count.
*
* #return the local Realm instance
* #throws IllegalStateException when no Realm is open
*/
public Realm get() {
Realm realm = localRealms.get();
if(realm == null) {
throw new IllegalStateException(
"No open Realms were found on this thread.");
}
return realm;
}
/**
* Closes local Realm instance, decrementing the reference count.
*
* #throws IllegalStateException if there is no open Realm.
*/
public void close() {
checkDefaultConfiguration();
Realm realm = localRealms.get();
if(realm == null) {
throw new IllegalStateException(
"Cannot close a Realm that is not open.");
}
realm.close();
// noinspection ConstantConditions
if(Realm.getLocalInstanceCount(Realm.getDefaultConfiguration()) <= 0) {
localRealms.set(null);
}
}
}
This way it is actually possible to use RealmManager class as single point of entry provided as #Provides #Singleton RealmManager realmManager(), and detach open() from get() thus allowing you to use an open Realm instance for a given thread (if it is already open), without risk of never being able to close it (because of calling Realm.getDefaultInstance() without a close() pair).
Then for background thread, you'd use
try {
realmManager.open();
...
} finally {
realmManager.close();
}
And in other methods inside you can do
Realm realm = realmManager.get();
As for UI thread, you'd use the retained fragment (or Activity/Fragment onCreate()/onDestroy()) as you normally would, but using open/close as well.
This way you can manage the Realm lifecycle in a single place, but still pass Realm as thread-local variable, instead of a global one (which would not work, as Realm instances are thread-confined).

Realm on Android: keep DB logic separate

I used to work with un-auto-managed model objects that were copied from Realm. I switched to using auto-managed Realm object for my new projects and have a problem.
Where before I could keep my DB logic separate, in DAO classes, right now, the Realm code is all over my application.
Realm objects should be managed (closed) well, everywhere, on every thread, on every activity and fragment. But what I dislike most: every setter on every model object needs to be in a Realm transaction. There is Realm all over my code right now!
Did anyone find a way to keep the DB logic somewhat separate, while using auto managed Realm objects?
Conclusion, after a few months with Realm auto-managed objects:
Having your database code centralised, in Data Access Object (DAO) classes or the like, is hardly possible when using Realm’s auto-updated objects.
Every setter on every model object needs to be inside a Realm transaction block. If you use RetroLambda, the calls are relatively clean:
realm.executeTransaction(r -> user.setFirstName(firstName));
These blocks will be all over your project within no time. Forgetting to wrap a setter method (or a constructor call) in a transaction will crash your app. Believe me, this will happen a lot in the first weeks of adopting auto-updated objects.
Auto-updated objects cannot be shared across threads. The same is true for Realm instances.
All threads, and often activities and fragments, will have to open and close Realm instances. You’ll be thinking constantly “On which thread am I?”. If you cross thread boundaries with model objects or Realm instances, you will crash your app.
More info here: https://medium.com/#ffvanderlaan/realm-auto-updated-objects-what-you-need-to-know-b2d769d12d76
and here: https://medium.com/#Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149#.gazrajqwt
What I did was created a RealmController class that handles all of my Realm transactions, and fetches new data from the API. The skeleton for the implementation is borrowed from another SO post which I can't find right now, but you should be able to find it on Google by searching RealmController.
public class RealmController {
private static RealmController instance;
private final Realm realm;
private ServiceInterface restInterface;
private final String TAG = "RealmController";
private static boolean browseFetchAllowed = true;
private RealmConfiguration config = new RealmConfiguration.Builder()
.name("myRealm")
.schemaVersion(0)
.deleteRealmIfMigrationNeeded()
.build();
public RealmController(Application application) {
restInterface = ServiceGenerator.createService(ServiceInterface.class);
realm = Realm.getInstance(config);
}
public void setAuthentication(String token, String uid) {
this.restInterface = ServiceGenerator.createService(ServiceInterface.class, token, uid);
}
public static RealmController with(Fragment fragment) {
if (instance == null) {
instance = new RealmController(fragment.getActivity().getApplication());
}
return instance;
}
public static RealmController with(Activity activity) {
if (instance == null) {
instance = new RealmController(activity.getApplication());
}
return instance;
}
public static RealmController with(Application application) {
if (instance == null) {
instance = new RealmController(application);
}
return instance;
}
public RealmController getInstance() {
return instance;
}
public ServiceInterface getRestInterface() {
return restInterface;
}
public Realm getRealm() {
return realm;
}
public RealmResults<MyRealmObject> getStuff() {
return realm.where(MyRealObject.class).findAll();
}
public void setStuff(RealmList<MyRealmObject> stuff) {
realm.beginTransaction();
realm.copyToRealmOrUpdate(stuff);
realm.commitTransaction();
}
public void getStuffFromServer() {
restInterface.getStuff().enqueue(new Callback<RealmList<MyRealmObject>>() {
#Override
public void onResponse(Call<RealmList<MyRealmObject>> call, Response<RealmList<MyRealmObject>> response) {
if (response.isSuccessful()) {
realm.beginTransaction();
realm.copyToRealmOrUpdate(response.body);
realm.commitTransaction();
}
}
#Override
public void onFailure(Call<RealmList<RealmAd>> call, Throwable t) {
t.printStackTrace();
}
});
}
}
Towards the bottom of the file I've added a few examples, but usage is quite simple:
RealmResults<MyRealmObject> results = RealmController.with(this).getStuff();
In terms of having the objects auto-managed, what I've been using is RealmResults, which is always auto-managed (even if you're querying for a single object). Then just add a change listener to the results of the query and voilà. Also, if you want to have auto-updating data in recyclerviews, I would recommend RealmRecyclerView.

Design solutions for fragments and asynchronous processing in Android

I struggled with some issues about design complex tasks with fragments
use. Fragments and asynchronous approach are quite new for me, so I think it will be better to describe my app.
Application
App works with GitHub API and has two screens: list of repositories and details about selected one. For retrieving data from json I use Retrofit and store it in SQLite. Little remark As I understood Retrofit can be used asynchronously but in case of additional work with DB it is better to use asynchronous approach for operations under DB also. In my case I'm checking internet connection: in case of absence I load data from DB. Otherwise I upgrade DB and then use it. Now I want to add fragments for different screen density support( usual master - detail workflow).
And my questions are
Where is the better place to run async tasks? Is it a right solution to make it in activity and then pass result to fragments?
What is the better solution for asynchronous processing? As I understood from my search about that, AsyncTask is deprecated but the easiest solution.
AsyncTask is a pain in the rear. Many beginners still seem to use it, but imho it's not worth learning it. Sooner or later you'll be bugged out by it because AsyncTask is error prone, has lots of caveats and tons of boilerplate code.
Retrofit does make it's calls asynchronously automatically, so you've got that covered already. Retrofit also plays very nice with RxJava which is I guess considered the way of doing asynchronous things on Android these days.
RxJava has a steeper learning curve initially than other patterns, but it's worth learning. If you got your database stuff working already, it won't be much work making ti asynchronous with Rx.
As for
Is it a right solution to make it in activity and then pass result to
fragments?
If you don't follow an MVP design approach, which is okay, in my opinion it's absolutely okay to do 'business logic' stuff in the Fragment and not let the Fragment call the Activity, then let the Activity get back to the Fragment. Whichever is easier for you and suits your app.
You can place your background thread anywhere, but make sure it is cancelled if the class is garbage collected, and don't keep references (Context, callbacks) in your thread.
public class MyActivity extends Activity {
private static interface OnDownloadThreadCompleteListener {
public void onDone(String data);
}
private static class DownloaderThread extends AsyncTask<Void, Void, String> {
private OnDownloadThreadCompleteListener mListener;
public DownloaderThread(OnDownloadThreadCompleteListener listener) {
mListener = listener;
}
#Override
protected String doInbackground(Void... args) {
// Do your network request here
return result;
}
#Override
public void onPostExecute(String data) {
if (mListener != null && !isCancelled()) {
mListener.onDone(data);
}
mListener = null;
}
#Override
public void onCancelled() {
mListener = null;
}
}
private DownloaderThread mThread;
private OnDownloadThreadCompleteListener mListener;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
mListener = new OnDownloadThreadCompleteListener() {
#Override
public void onDone(String data) {
Fragment fragment = getFragmentManager().findFragmentByTag("fragment_git");
fragment.show(data);
}
}
mThread = new DownloaderThread(mListener);
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mThread.execute(null, null, null);
}
}
}
#Override
public void onPause() {
super.onPause();
if (mThread != null) {
mThread.cancel(true);
}
mThread = null;
mListener = null
}
}

Categories

Resources