Resume flowable converted to live data after scree

2020-04-19 08:56发布

问题:

Say I have an activity like this:

public class TestActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final TextView countdown = new TextView(this);
        setContentView(countdown);

        ViewModelProviders.of(this)
                .get(TestViewModel.class)
                .getCountdown()
                .observe(this, countdown::setText);
    }
}

And the view model is:

class TestViewModel extends ViewModel {
    private final LiveData<String> countdown =
            LiveDataReactiveStreams.fromPublisher(
                    Flowable.concat(
                            Flowable.just("Falcon Heavy rocket will launch in..."),
                            Flowable.intervalRange(0, 10, 3, 1, TimeUnit.SECONDS)
                                    .map(x -> String.valueOf(10 - x)),
                            Flowable.timer(1, TimeUnit.SECONDS)
                                    .map(ignored -> "Lift off!")
                    )
            );

    LiveData<String> getCountdown() {
        return countdown;
    }
}

I want to handle the rotation correctly, so if the user rotates his device while the countdown says 5, I want the next value after rotation to be 5 (or 4 if the second elapsed already), just pick up wherever the countdown should actually be.

If the rocket has already lifted off, I want it to keep it that way after rotation, I don't want the countdown to start again.

At the moment, LiveDataReactiveStreams cancels the subscription on pause and makes a new subscription on resume, so the countdown gets restarted.

I'm guessing I should change something in my RxJava part of the code for this to work. Any ideas as to what to change?

回答1:

Thanks to Svyatoslav Lebeckiy over at Android United Slack channel who pointed out BehaviorSubject and BehaviorProcessor to me and proposed an idea on how to fix this.

I changed my view model to:

class TestViewModel extends ViewModel {

    private LiveData<String> countdown;

    LiveData<String> getCountdown() {
        if (countdown == null) {
            countdown = LiveDataReactiveStreams.fromPublisher(startCountdown());
        }
        return countdown;
    }

    private static Flowable<String> startCountdown() {
        final BehaviorProcessor<String> processor = BehaviorProcessor.create();
        Flowable.concat(
                Flowable.just("Falcon Heavy rocket will launch in..."),
                Flowable.intervalRange(0, 10, 3, 1, TimeUnit.SECONDS)
                        .map(x -> String.valueOf(10 - x)),
                Flowable.timer(1, TimeUnit.SECONDS)
                        .map(ignored -> "Lift off!")
        ).subscribe(processor);
        return processor;
    }
}

This way I can start the countdown only once in getCountdown and the BehaviorProcessor created in startCountdown takes care of delivering the last and subsequently emitted values to its subscribers (LiveData in this case).

Since LiveDataReactiveStreams needs a Flowable, it's convenient to use a BehaviorProcessor instead of BehaviorSubject here.