title: Realm instances and multithreading
body: <p>I'm trying to code something to get offline some content that could be watched later. This content is made of jsons, images, vidéos and pdfs : </p>
  
  <pre><code>{
    "elements": [
      {
        "id":"3b4c4f3da8bf9d1527010c5242e037b7",
        "type":"media"
      },
      ...
    ],
    "id":"58088318ef0b4832f6c0e70b",
    "content": "Hello World"
  }
  </code></pre>
  
  <p>So basically my problem is that I switch between async network calls and realm db updates, and I can't figure out how to build it well.</p>
  
  <p>I fist fetch the above Json must store it in realm, then I call a second route for each element to get <code>DetailedElement</code> and store it aswell. when an element contains a downloadable document I must dowload it and add its path as a member of <code>DetailedElement</code>. Thing is, I can't get my threads right.</p>
  
  <p>I'd idealy like a method with this signature : </p>
  
  <p><code>FooBar.prefetch(Context ctx, String id, Callback cb)</code></p>
  
  <p>My first step should be to ensure it's launched in a non-UI Looper thread : I'm not sure how to do it </p>
  
  <pre><code>public static void Bar(final Context ctx, final String id) {
      new Thread(new Runnable() {
          @Override
          public void run() {
              Looper.prepare();
              final Realm realm = Realm.getDefaultInstance();
              final Handler handler = new Handler();
  
              MainThingToDownload mainThingToDownload = realm.where(MainThingToDownload.class).equalTo("id", id).findFirst();
              if (mainThingToDownload != null) {
                  processMain(mainThingToDownload, realm, handler);
              } else {
                  Api.getInstance().backendRepresentation.getMainThing(id).enqueue(new CustomRetrofitCallBack&lt;&gt;(null) {
                      @Override
                      public void onResponseReceived(final MainThingToDownload mainThingToDownload) {
  
                          handler.post(new Runnable() {
                              @Override
                              public void run() {
                                  realm.executeTransactionAsync(new Realm.Transaction() {
                                      @Override
                                      public void execute(Realm realm) {
                                          realm.copyToRealmOrUpdate(mainThingToDownload);
                                          processMain(mainThingToDownload, realm, handler);
                                      }
                                  });
                              }
                          });
                      }
                  });
              }
          }
      });
  }
  </code></pre>
  
  <p>Is it the right approach ? keeping the realm reference and the handler reference for its thread. And then I always do in my network callbacks something like that : </p>
  
  <pre><code>    handler.post(new Runnable() {
          @Override
          public void run() {
              realm.executeTransactionAsync(new Realm.Transaction() {
                  @Override
                  public void execute(Realm realm) {
                      //... add downloaded object to realm or modifies one     
                  }
              });     
          }
      });
  </code></pre>
  
  <p>I had lot of different errors with my code. Most of the time I don't use Realm on the thread where it should be used on and sometimes I use a realm instance that has already been closed. </p>
  
  <p>Is this how I'm suppose to do it ? 
  Thanks,</p>
  
  <hr>
  
  <p><em>What I did</em></p>
  
  <p>A <code>Program</code> contains a list of <code>Module</code>, each <code>Module</code> contains a list of <code>Element</code>.</p>
  
  <p><strong>ProgramOfflineManager</strong></p>
  
  <pre><code>public class ProgramOfflineManager extends AbstractOfflineManager {
  
      private final static String TAG = "ModuleOfflineManager";
  
      public ProgramOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
          super(ctx, id, errorDisplayerInterface);
      }
  
      @Override
      protected void makeOffline() throws IOException {
          super.makeOffline();
          ProgramOfflineManager.makeOffline(id, ctx);
      }
  
      @Override
      protected void removeOffline() {
          super.removeOffline();
          ProgramOfflineManager.removeOffline(id, ctx);
      }
  
      public static void removeOffline(String programId, Context ctx) {
  
          Log.i(TAG, "to be deleted");
          Realm realm = null;
          try {
              realm = Realm.getDefaultInstance();
              //To be deleted
              final Program program = realm.where(Program.class).equalTo("id", programId).findFirst();
              Log.i("renaud", "courseDetailed matching is is not null : (detailed!=null)=&gt;" + (program != null));
              if (program != null) {
                  for (Module module : program.getModules()) {
                      CourseDetailed courseDetailed = realm.where(CourseDetailed.class).equalTo("id", module.getCourse()).equalTo("downloaded", true).findFirst();
                      if (courseDetailed != null) {
                          ModuleOfflineManager.removeOffline(module.getCourse(), ctx);
                      }
                  }
                  realm.executeTransaction(new Realm.Transaction() {
                      @Override
                      public void execute(Realm realm) {
                          program.deleteFromRealm();
                          Log.i(TAG, "course has been deleted");
                      }
                  });
              }
          } finally {
              if (realm != null) {
                  realm.close();
              }
              realm = null;
          }
      }
  
      public static void makeOffline(final String programId, final Context ctx) throws IOException {
          Api.Service360Interface backend = Api.getInstance().backend;
  
          Response&lt;Program&gt; programResponse = backend.getProgram(programId).execute();
  
          if (programResponse.isSuccessful()) {
              final Program program = programResponse.body();
  
              Realm realm = null;
              try {
                  realm = Realm.getDefaultInstance();
  
                  realm.executeTransaction(new Realm.Transaction() {
                      @Override
                      public void execute(Realm realm) {
                          program.setDownloaded(true);
                          realm.copyToRealmOrUpdate(program);
                      }
                  });
  
                  for (final Module module : program.getModules()) {
                      long count = realm.where(CourseDetailed.class).equalTo("id", module.getCourse()).equalTo("downloaded", true).count();
                      if (count == 0) {
                          ModuleOfflineManager.makeOffline(module.getCourse(), ctx);
                      }
                  }
  
  
              } finally {
                  if (realm != null) {
                      realm.close();
                  }
                  realm = null;
              }
          }
  
      }
  
  
  }
  </code></pre>
  
  <p><strong>ModuleOfflineManager</strong></p>
  
  <pre><code>public class ModuleOfflineManager extends AbstractOfflineManager {
  
      private final static String TAG = "ModuleOfflineManager";
  
      public ModuleOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
          super(ctx, id, errorDisplayerInterface);
      }
  
      @Override
      protected void makeOffline() throws IOException {
          super.makeOffline();
          ModuleOfflineManager.makeOffline(id, ctx);
      }
  
      @Override
      protected void removeOffline() {
          super.removeOffline();
          ModuleOfflineManager.removeOffline(id, ctx);
      }
  
      public static void removeOffline(String courseId, Context ctx) {
  
          Log.i(TAG, "to be deleted");
          Realm realm = null;
          try {
              realm = Realm.getDefaultInstance();
              //To be deleted
              final CourseDetailed detailed = realm.where(CourseDetailed.class).equalTo("id", courseId).findFirst();
              Log.i("renaud", "courseDetailed matching is is not null : (detailed!=null)=&gt;" + (detailed != null));
              if (detailed != null) {
                  for (Element element : detailed.getElements()) {
                      Log.i(TAG, "next Element to suppress : " + element.getId());
                      final CourseElement courseElement = realm.where(CourseElement.class).equalTo("id", element.getId()).findFirst();
                      if (courseElement.getCollection() != null &amp;&amp; courseElement.getCollection() == PostCollectionType.MEDIAS) {
                          Log.i(TAG, "it's a Media, erasing from db");
                          MediaDownloadUtils.eraseMedia(ctx, courseElement, realm);
                      }
                      realm.executeTransaction(new Realm.Transaction() {
                          @Override
                          public void execute(Realm realm) {
                              courseElement.deleteFromRealm();
                              Log.i(TAG, "element has been deleted");
                          }
                      });
  
                  }
                  realm.executeTransaction(new Realm.Transaction() {
                      @Override
                      public void execute(Realm realm) {
                          detailed.deleteFromRealm();
                          Log.i(TAG, "course has been deleted");
                      }
                  });
              }
          } finally {
              if (realm != null) {
                  realm.close();
              }
              realm = null;
          }
      }
  
      public static void makeOffline(final String courseId, final Context ctx) throws IOException {
          Api.Service360Interface backend = Api.getInstance().backend;
  
          Response&lt;CourseDetailed&gt; response = backend.getCourse(courseId).execute();
          if (response.isSuccessful()) {
              final CourseDetailed courseDetailedResponse = response.body();
  
              Realm realm = null;
              try {
                  realm = Realm.getDefaultInstance();
  
                  realm.executeTransaction(new Realm.Transaction() {
                      @Override
                      public void execute(Realm realm) {
                          courseDetailedResponse.saveEnums();
                          courseDetailedResponse.setDownloaded(true);
                          realm.copyToRealmOrUpdate(courseDetailedResponse);
                      }
                  });
  
                  for (final Element element : courseDetailedResponse.getElements()) {
  
                      Call&lt;CourseElement&gt; call = Api.getInstance().getCourseElement(element.getCollection(), element.getId(), courseId);
                      Response&lt;CourseElement&gt; courseElementResponse = call.execute();
                      if (courseElementResponse.isSuccessful()) {
                          final CourseElement courseElement = courseElementResponse.body();
                          realm.executeTransaction(new Realm.Transaction() {
                              @Override
                              public void execute(Realm realm) {
                                  courseElement.setCourseElementType(CourseElementTypes.valueOf(element.getCollection()));
                                  courseElement.saveEnums();
                                  courseElement.setDownloaded(true);
                                  realm.copyToRealmOrUpdate(courseElement);
  
                                  MediaDownloadUtils.prefechMedia(ctx, courseElement);
                              }
                          });
                      }
                  }
              } finally {
                  if (realm != null) {
                      realm.close();
                  }
                  realm = null;
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>AbstractOfflineManager</strong></p>
  
  <pre><code>public abstract class AbstractOfflineManager implements OfflineManagerInterface {
  
      private final static String TAG = "AbstractOfflineManager";
  
      final protected Context ctx;
      final protected String id;
      final protected ErrorDisplayerInterface errorDisplayerInterface;
  
      protected boolean status;
  
      public AbstractOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
          this.ctx = ctx;
          this.id = id;
          this.errorDisplayerInterface = errorDisplayerInterface;
      }
  
      protected void makeOffline() throws IOException {
          //implementations in children
      }
  
      protected void removeOffline() {
          //implementations in children
      }
  
      @Override
      public CompoundButton.OnCheckedChangeListener getClickListener() {
          return new CompoundButton.OnCheckedChangeListener() {
              @Override
              public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
  
                  Log.i(TAG, "clic ! isChecked : " + isChecked);
  
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          Looper.prepare();
                          status = isChecked;
                          if (isChecked) {
                              try {
                                  makeOffline();
                              } catch (IOException e) {
                                  e.printStackTrace();
                                  errorDisplayerInterface.popError(null, e);
                              }
                          } else {
                              removeOffline();
                          }
                      }
                  }).start();
              }
          };
      } }
  </code></pre>
  
  <p>Now I'll create a <code>ElementOfflineManager</code> </p>