2017-03-27 1 views
1

Je crée une petite application en utilisant Mosby.Android Mosby MVI lié au service dans le présentateur

L'application dispose d'un service auquel je souhaite me lier. Je suppose que le bon endroit pour le faire est dans le présentateur. Mais je ne peux pas vraiment comprendre comment le faire. Ce que je veux archiver, c'est quand le service est lié, je veux appeler une méthode dessus et pousser cette valeur à la vue, de sorte que l'état est correct maintenant. Lorsque le service envoie des mises à jour sur le bus d'événements, je souhaite également le transmettre à la vue.

J'ai trouvé un exemple sur la partie suivante, mais rien sur la façon de lier/dissocier le service dans le présentateur.

Mon coup de couteau sur elle était de créer quelque chose comme ça dans l'activité:

@NonNull 
@Override 
public MyPresenter createPresenter() { 
    return new MyPresenter(new MyService.ServiceHandler() { 
      @Override 
      public void start(ServiceConnection connection) { 
       Intent intent = new Intent(MyActivity.this, MyService.class); 
       startService(intent); 
       bindService(intent, connection, Context.BIND_AUTO_CREATE); 
      } 

      @Override 
      public void stop(ServiceConnection connection) { 
       unbindService(connection); 
      } 
     }); 

Et puis dans le présentateur faire quelque chose comme ceci:

private ServiceConnection connection; 
private boolean bound; 
private MyService service; 

public MyPresenter(MyService.ServiceHandler serviceHandler) { 
    super(new MyViewState.NotInitialiezedYet()); 

    this.serviceHandler = serviceHandler; 

    connection = new ServiceConnection() { 
     @Override 
     public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 
      MyService.LocalBinder binder = (MyService.LocalBinder) service; 
      service = binder.getService(); 
      bool isInitialized = service.isInitialized(); 
      // how do i push isInitialized to view? 

     } 

     @Override 
     public void onServiceDisconnected(ComponentName componentName) { 

     } 
    }; 
} 

@Override 
public void attachView(@NonNull SplashView view) { 
    super.attachView(view); 
    serviceHandler.start(connection); 
    bound = true; 
} 

@Override 
public void detachView(boolean retainInstance) { 
    super.detachView(retainInstance); 
    if(bound) { 
     serviceHandler.stop(connection); 
     bound = false; 
    } 
} 

@Override 
protected void bindIntents() { 
    //Not sure what this would look like? 
} 

public void onEventInitialized(InitializedEvent event) { 
    //how do I push this to the view? 
} 

Suis-je sur le bon chemin? Quelle serait la bonne façon de faire cela? Comment est-ce que je devrais envoyer la valeur du service à la vue dans onServiceConnected et quand j'obtiens des événements sur le bus d'événement dans onEventInitialized?

Répondre

4

Quelques choses à noter avant de nous plonger dans une mise en œuvre possible:

  1. dans Mosby, présentateurs survivons orientation de l'écran par défaut et que la vue est attaché/détaché. Si vous créez un ServiceHandler dans votre Activity vous avez une fuite de mémoire parce que ServiceHandler est une classe annoncée instanciée dans votre activité et a donc une référence à l'instance d'activité externe. Pour éviter cela, vous pouvez utiliser votre classe Application comme contexte pour appeler bindService() et unbindService(). Les services sont une logique métier, il vaut donc mieux mettre cette logique de service de liaison non dans le calque Vue (activité), mais plutôt dans son propre composant "logique métier", c'est-à-dire appelons ce composant MyServiceInteractor.
  2. Lorsque vous déplacez cette pièce dans une logique métier, vous vous demandez peut-être quand déconnecter/arrêter le service. Dans votre code, vous l'avez fait dans Presenter detachView(). Alors que cela fonctionne, Presenter a maintenant une connaissance explicite des composants internes de la logique métier et de leur fonctionnement. Une solution similaire pour les Rx serait de lier le cycle de vie de la connexion de service au "cycle de vie" d'un Rx Observable. Cela signifie que la connexion de service doit être fermée, une fois que l'observable est désabonné/éliminé. Cela correspond également parfaitement à 1. "Le présentateur survit aux changements d'orientation de l'écran" (et maintient les abonnements observables en vie pendant les changements d'orientation de l'écran).
  3. Tout rappel/écouteur peut facilement être "enveloppé" dans un Rx Observable en utilisant Observable.create().
  4. Personnellement, je trouve que les services (en particulier les services limités) sont lourds à manipuler et introduisent une complexité beaucoup plus grande dans votre code. Vous pourriez (ou ne pourriez pas) être capable de faire la même chose sans services. Mais cela dépend vraiment de votre application concrète/cas d'utilisation.

Cela dit, nous allons voir comment une solution pourrait ressembler (code pseudo identiques, peuvent ne pas compiler):

public class MyServiceInteractor { 

    private Context context; 

    public MyServiceInteractor(Context context) { 
    this.context = context.getApplicationContext(); 
    } 

    public Observable<InitializedEvent> getObservable() { 
    return Observable.create(emitter -> { 
     if (!emitter.isDisposed()) { 

     MyService.ServiceHandler handler = new MyService.ServiceHandler() { 

      @Override public void start(ServiceConnection connection) { 
      Intent intent = new Intent(context, MyService.class); 
      context.startService(intent); 
      context.bindService(intent, connection, Context.BIND_AUTO_CREATE); 
      } 

      @Override public void stop(ServiceConnection connection) { 
      context.unbindService(connection); 
      } 
     }; 

     emitter.onNext(handler); 
     emitter.onComplete(); 
     } 
    }).flatMap(handler -> 
     Observable.create(emitter -> { 
      ServiceConnection connection = new ServiceConnection() { 
      @Override public void onServiceConnected(ComponentName name, IBinder service) { 
       MyService.LocalBinder binder = (MyService.LocalBinder) service; 
       MyService service = binder.getService(); 
       boolean isInitialized = service.isInitialized(); 
       if (!emitter.isDisposed()) 
       emitter.onNext(new InitializedEvent(isInitialized)); 
      } 

      @Override public void onServiceDisconnected(ComponentName name) { 
       // you may want to emit an event too 
      } 
      }; 

     }) 
     .doOnDispose({handler.stop()}) 
    ); 
    } 
} 

Donc, fondamentalement MyServiceInteractor.getObservable() crée un pont au monde Rx Observable et arrête la connexion de service lorsque le get observable est désinscrit. Notez que cet extrait de code ne peut pas être compilé. C'est juste pour illustrer à quoi pourrait ressembler une solution/workflow possible.

Ensuite, votre Presenter pourrait ressembler à ceci:

public class MyPresenter extends MviBasePresenter<MyView, InitializedEvent> { 
    private MyServiceInteractor interactor; 

    public MyPresenter(MyServiceInteractor interactor){ 
    this.interactor = interactor; 
    } 

    @Override 
    void bindIntents(){ 
    Observable<InitializedEvent> o = intent(MyView::startLoadingIntent) // i.e triggered once in Activity.onStart() 
     .flatMap(ignored -> interactor.getObservable()); 

    subscribeViewState(o, MyView::render); 
    } 
} 

Ainsi, la principale question/problème ici est pas très MVI ou MVP ou MVVM spécifique, il est surtout la façon dont nous ne « envelopper » les callbacks de service android en RxJava observable. Une fois que nous aurons ceci, le reste devrait être facile. La seule chose liée à MVI est de connecter les points: La vue doit réellement déclencher une intention de démarrer la connexion de service. Ceci est fait en bindIntents() via myView.startLoadingIntent()

J'espère que cela aide.

+0

Un grand merci pour la réponse rapide. Merci également pour tous les articles du blog! Je vais essayer ça. –

+0

Ok, j'ai essayé d'envelopper ma tête mais je ne comprends pas. J'ai un service d'arrière-plan qui diffuse de l'audio. Je dois être en mesure d'appeler à côté, jouer, faire une pause sur le service via bind. du service je reçois des événements comme la prochaine piste, le progrès etc. À l'heure actuelle il y a un eventbus entre l'activité et le service. Vaut-il mieux obtenir un observable du service? À quoi ressemblerait un Interactor? Vous pouvez envoyer un événement de piste suivant à partir de l'interface utilisateur qui est envoyée au serveur et vous obtenez également un nouveau modèle de piste. Je suis nouveau sur Rx, donc il y a beaucoup de choses nouvelles. –

+0

Ensuite, le seul besoin d'un service est que vous souhaitiez que le lecteur audio fonctionne en arrière-plan. Créez une classe 'AudioPlayer' avec une méthode' Observable getStateObservable() '(retourne l'état actuel du lecteur comme PLAYING XY ou PAUSED). Votre 'AudioPlayerInteractor' utilisé par' Presenter' s'abonne à cet état pour l'afficher dans la vue. Votre 'Service' partage la même instance' AudioPlayer' que 'AudioPlayerInteractor' mais contrôle juste le joueur comme si vous appeliez' audioPlayer.pause() '. Le service n'a pas à être lié, il suffit d'utiliser Android Intents pour communiquer avec 'Service'. – sockeqwe