2017-10-05 6 views
2

C'est le ViewModel où les transactions Rx sont appeléesComment empêcher le problème Realm Threading lors de l'utilisation d'un appel Realm dans une transaction Rx Observable?

RealmHelperRepo est la mise en œuvre de l'interface HelperRepo

@PerActivity 
public class RoleSelectionViewModel extends BaseViewModel<RoleSelectionMvvm.View> implements RoleSelectionMvvm.ViewModel { 

    private Disposable roleGroupSubscription; 

    @Inject 
     public RoleSelectionViewModel(@AppContext Context context, HelperRepo helperRepo, ApiOAuth2 ApiOAuth2) { 

     this.mContext = context; 
     this.mUserRepo = userRepo; 
     this.mHelperRepo = helperRepo; 
     ApiOAuth2.initialize(); 
     this.mApiOAuth2 = ApiOAuth2; 

     this.mCurrentUser = mUserRepo.getByField("isLoggedIn", true, true); 
     if (mCurrentUser != null) { 
      this.mCurrentUserId = mCurrentUser.getId(); 
      this.mHelper = mHelperRepo.getByField("user.id", mCurrentUserId, true); 
    } 

    Observable<Response<ResponseHelper>> postHelperObservable = mApiOAuth2.postHelperRX(new Helper()); 
    Observable<Response<ResponseHelper>> getHelperObservable = mApiOAuth2.getHelperRX(mCurrentUserId); 

roleGroupSubscription = postRoleGroupsObservable 
        .subscribeOn(Schedulers.io()) 
        .observeOn(AndroidSchedulers.mainThread()) 
        .flatMap((response) -> { 
         if (response.isSuccessful()) { 
          ResponseHelper responseHelper = response.body(); 
          mHelper = responseHelper.getHelper(); 
          return Observable.just(mHelper); 
         } else if (response.code() == 409) { 
         // handle POST conflict (i.e. helper already exists) 
          return getHelperObservable; 
         } 

        }) 
        .subscribe((data) -> { 
         if (data instanceof Response<?>) { 
          // data came from getHelperObservable 
          Response response = (Response) data; 
          if (!response.isSuccessful()) { 
           ResponseHelper responseHelper = (ResponseHelper) response.body(); 
          mHelper = responseHelper.getHelper(); 
         else { 

          // data came from Observable.just(helper) 
          mApiOAuth2.getHelperRX(mCurrentUserId).subscribe(
            responseHelperResponse -> { 

             if (responseHelperResponse.isSuccessful()) { 

              String helperID = responseHelperResponse.body().getHelper().getId(); 
              Log.d("RealmCount", "save: " + Realm.getLocalInstanceCount(realmProvider.get().getConfiguration())); 
              mHelper.setId(helperID); 
              mHelper.setUser(mCurrentUser); 
--------> // when mHelperRepo.save(mHelper) is called, it goes to RealmHelperRepo to save and 
--------> // thus triggering mRealm.executeTransaction causing Realm threading 
              mHelperRepo.save(mHelper); 
             } 
             saveAndBegin(); 
            }, 
            Throwable::printStackTrace); 
            }); 

C'est la classe RealmRepo où les appels de royaume sont faits.

@PerApplication 
public class RealmHelperRepo implements HelperRepo { 

    private final Provider<Realm> mRealmProvider; 
    private Realm mRealm; 

    @Inject 
    public RealmHelperRepo(Provider<Realm> realmProvider) { 
     this.mRealmProvider = realmProvider; 
     this.mRealm = mRealmProvider.get(); 
} 


    @Override 
    public void save(Helper helper) { 
     if (mRealm != null) { 
---------> // code runs into threading issue here when a realmThread executeTransaction is called 
     mRealm.executeTransaction(r -> r.copyToRealmOrUpdate(helper)); 
     } 
    } 

Y at-il quelque chose qui me manque ici? Autres fonctions Rx que je devrais utiliser au lieu de flatmap? Existe-t-il d'autres moyens de sauvegarder mes données observables sans rencontrer de problème de threading? Aidez-moi!

Répondre

2

Y at-il quelque chose qui me manque ici?

Un exemple Realm représente une référence compté , instance thread local. Ce n'est pas une chose globale, c'est une "instance locale" qui est ouverte par getInstance() puis fermée par close().

Vous ne pouvez donc pas simplement initialiser une instance de domaine en tant que singleton, car elle ne sera pas accessible à partir des threads d'arrière-plan.


Vous pouvez par exemple fournir une classe de gestionnaire de domaine singleton capable d'ouvrir des instances de domaine locales.

/** 
* The RealmManager allows creating a singleton Realm manager which can open thread-local instances. 
* 
* It also allows obtaining the open thread-local instance without incrementing the reference count. 
*/ 
@PerApplication 
public class RealmManager { 
    private final ThreadLocal<Realm> localRealms = new ThreadLocal<>(); 

    @Inject 
    RealmManager() { 
    } 

    /** 
    * Opens a reference-counted local Realm instance. 
    * 
    * @return the open Realm instance 
    */ 
    public Realm openLocalInstance() { 
     checkDefaultConfiguration(); 
     Realm realm = Realm.getDefaultInstance(); // <-- maybe configuration should be constructor parameter 
     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 getLocalInstance() { 
     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 closeLocalInstance() { 
     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); 
     } 
    } 

    private void checkDefaultConfiguration() { 
     if(Realm.getDefaultConfiguration() == null) { 
      throw new IllegalStateException("No default configuration is set."); 
     } 
    } 
} 

Vous pouvez utiliser cela comme si

@PerApplication 
public class RealmHelperRepo implements HelperRepo { 
    private final RealmManager realmManager; 

    @Inject 
    public RealmHelperRepo(RealmManager realmManager) { 
     this.realmManager = realmManager; 
    } 


    @Override 
    public void save(Helper helper) { 
     try(Realm realm = realmManager.openLocalInstance()) { 
      realm.executeTransaction(r -> r.copyToRealmOrUpdate(helper)); 
     } 
    } 

Techniquement, il se cache tout simplement l'appel Realm.getDefaultInstance() et vous permet d'obtenir votre instance de thread local même sans incrémenter le compteur de référence de RealmCache interne, donc pas beaucoup réel magie là-bas.

Il suffit d'ouvrir une instance de domaine pour un thread et n'oubliez pas de le fermer quand il n'est plus nécessaire.

+0

merci pour la clarification! va travailler dessus :) –

+0

sauveteur merci !! –