Giter Site home page Giter Site logo

Add DialogScopeProvider module about autodispose HOT 7 CLOSED

uber avatar uber commented on April 28, 2024
Add DialogScopeProvider module

from autodispose.

Comments (7)

ZacSweers avatar ZacSweers commented on April 28, 2024

I'd be open to adding this to the Android artifact. Just simple show/dismiss to keep things simple should be fine. Want to contribute a PR?

from autodispose.

 avatar commented on April 28, 2024

After I wrote some codes for this artifact. I realize Dialog only has the setShowListener() and setDissmissListener() , not the addShowListener() and addDissmissListener() , so we need to custom OnShowListener and OnDissmissListener interface. It's bad.

So why not use the ViewScopeProvider.from(Dialog.getWindow().getDecordView()) instead ?
But there is a problem. After i call Dialog.show(), dialog DecordView state is still Detached, the ViewScopeProvider throws

OutsideLifecycleException("View is detached!")

So hope to change ViewScopeProvider.java

public class ViewScopeProvider implements LifecycleScopeProvider<ViewLifecycleEvent> {
  private static final Function<ViewLifecycleEvent, ViewLifecycleEvent> CORRESPONDING_EVENTS =
      new Function<ViewLifecycleEvent, ViewLifecycleEvent>() {
        @Override public ViewLifecycleEvent apply(ViewLifecycleEvent lastEvent) throws Exception {
          switch (lastEvent) {
            case ATTACH:
              return DETACH;
            default:
              throw new OutsideLifecycleException("View is detached!");
          }
        }
      };

to

public class ViewScopeProvider implements LifecycleScopeProvider<ViewLifecycleEvent> {
  private static final Function<ViewLifecycleEvent, ViewLifecycleEvent> CORRESPONDING_EVENTS =
      new Function<ViewLifecycleEvent, ViewLifecycleEvent>() {
        @Override public ViewLifecycleEvent apply(ViewLifecycleEvent lastEvent) throws Exception {
          switch (lastEvent) {
            case ATTACH:
              return DETACH;
            default:
              // throw new OutsideLifecycleException("View is detached!");
              return DETACH;
          }
        }
      };

from autodispose.

ZacSweers avatar ZacSweers commented on April 28, 2024

That change is incorrect behavior as the exception is being thrown correctly. The reason you get that exception is because window changes are actually asynchronous, so it is actually detached at the time. You would have to delay subscription until attach. I don't think decorview attaches is the correct way to handle dialogs anyway.

I don't think a setter is terrible, but it is less than ideal. I'd be fine with putting an example using the setter in the sample app.

from autodispose.

tata8k avatar tata8k commented on April 28, 2024

Maybe we can modify ViewScopeProvider.java like this

public class ViewScopeProvider implements LifecycleScopeProvider<ViewLifecycleEvent> {
  //add
  private final Dialog dalog;
 
  //add
  public static LifecycleScopeProvider from(Dialog dialog) {
    if (dialog == null) {
      throw new NullPointerException("dialog == null");
    }
    return new ViewScopeProvider(dialog);
  }

  //add
  private ViewScopeProvider(final Dialog dialog) {
    this.dialog = dialog;
    this.view = dialog.getWindow().getDecordView();
    lifecycle = new ViewAttachEventsObservable(view);
  }

    //modify
  @Override public ViewLifecycleEvent peekLifecycle() {
    if(dialog != null){
       return dialog.isShowing() ? ViewLifecycleEvent.ATTACH : DETACH;
   }
   return AutoDisposeAndroidUtil.isAttached(view) ? ViewLifecycleEvent.ATTACH : DETACH;
}

from autodispose.

ZacSweers avatar ZacSweers commented on April 28, 2024

No, ViewScopeProvider should not know anything or care about dialogs

from autodispose.

tata8k avatar tata8k commented on April 28, 2024

I have written the codes

DialogScopeProvider.java , DialogScopeProviderTest.java and AutoDisposeTestActivity.java

DialogScopeProvider.java

/*
 * Copyright (C) 2017. Uber Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.uber.autodispose.android;

import android.app.Dialog;
import com.uber.autodispose.LifecycleScopeProvider;
import com.uber.autodispose.OutsideLifecycleException;
import com.uber.autodispose.android.internal.AutoDisposeAndroidUtil;
import io.reactivex.Observable;
import io.reactivex.functions.Function;

import static com.uber.autodispose.android.ViewLifecycleEvent.DETACH;

/**
 * A {@link LifecycleScopeProvider} that can provide scoping for Android {@link Dialog} classes.
 * <p>
 * <pre><code>
 *   AutoDispose.with(DialogScopeProvider.from(dialog));
 * </code></pre>
 */
public class DialogScopeProvider implements LifecycleScopeProvider<ViewLifecycleEvent> {

  private static final Function<ViewLifecycleEvent, ViewLifecycleEvent> CORRESPONDING_EVENTS =
      new Function<ViewLifecycleEvent, ViewLifecycleEvent>() {
        @Override public ViewLifecycleEvent apply(ViewLifecycleEvent lastEvent) throws Exception {
          switch (lastEvent) {
            case ATTACH:
              return DETACH;
            default:
              throw new OutsideLifecycleException("Dialog is dismiss!");
          }
        }
      };

  private final Observable<ViewLifecycleEvent> lifecycle;
  private final Dialog dialog;

  /**
   * Creates a {@link LifecycleScopeProvider} for Android Dialog.
   *
   * @param dialog the dialog to scope for
   * @return a {@link LifecycleScopeProvider} against this dialog.
   */
  public static DialogScopeProvider from(Dialog dialog) {
    if (dialog == null) {
      throw new NullPointerException("dialog == null");
    }
    return new DialogScopeProvider(dialog);
  }

  private DialogScopeProvider(final Dialog dialog) {
    this.dialog = dialog;
    lifecycle = new ViewAttachEventsObservable(dialog.getWindow().getDecorView());
  }

  @Override public Observable<ViewLifecycleEvent> lifecycle() {
    return lifecycle;
  }

  @Override public Function<ViewLifecycleEvent, ViewLifecycleEvent> correspondingEvents() {
    return CORRESPONDING_EVENTS;
  }

  @Override public ViewLifecycleEvent peekLifecycle() {
    if (dialog.isShowing() || AutoDisposeAndroidUtil.isAttached(
        dialog.getWindow().getDecorView())) {
      return ViewLifecycleEvent.ATTACH;
    }
    return ViewLifecycleEvent.DETACH;
  }
}

DialogScopeProviderTest.java

/*
 * Copyright (C) 2017. Uber Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.uber.autodispose.android;

import android.app.Dialog;
import android.app.Instrumentation;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.OutsideLifecycleException;
import com.uber.autodispose.android.internal.AutoDisposeAndroidUtil;
import com.uber.autodispose.test.RecordingObserver;
import io.reactivex.disposables.Disposable;
import io.reactivex.subjects.PublishSubject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static com.google.common.truth.Truth.assertThat;

@RunWith(AndroidJUnit4.class) public final class DialogScopeProviderTest {

  private static final RecordingObserver.Logger LOGGER = new RecordingObserver.Logger() {
    @Override public void log(String message) {
      Log.d(DialogScopeProviderTest.class.getSimpleName(), message);
    }
  };

  @Rule public final ActivityTestRule<AutoDisposeTestActivity> activityRule =
      new ActivityTestRule<>(AutoDisposeTestActivity.class);

  private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();

  private Dialog dialog;

  @Before public void setUp() {
    AutoDisposeTestActivity activity = activityRule.getActivity();
    dialog = activity.dialog;
  }

  @Test public void observable_normal() {
    final RecordingObserver<Integer> o = new RecordingObserver<>(LOGGER);
    final PublishSubject<Integer> subject = PublishSubject.create();

    // Show dialog
    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        dialog.show();
      }
    });

    assertThat(dialog.isShowing()).isTrue();
    assertThat(AutoDisposeAndroidUtil.isAttached(dialog.getWindow().getDecorView())).isFalse();

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        subject.to(AutoDispose.with(DialogScopeProvider.from(dialog)).<Integer>forObservable())
            .subscribe(o);
      }
    });

    Disposable d = o.takeSubscribe();
    o.assertNoMoreEvents(); // No initial value.

    subject.onNext(0);
    assertThat(o.takeNext()).isEqualTo(0);

    subject.onNext(1);
    assertThat(o.takeNext()).isEqualTo(1);

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        dialog.dismiss();
      }
    });

    assertThat(dialog.isShowing()).isFalse();
    assertThat(AutoDisposeAndroidUtil.isAttached(dialog.getWindow().getDecorView())).isFalse();

    subject.onNext(2);
    o.assertNoMoreEvents();
    d.dispose();
  }

  @Test public void observable_offMainThread_shouldFail() {
    RecordingObserver<Integer> o = new RecordingObserver<>(LOGGER);
    PublishSubject<Integer> subject = PublishSubject.create();

    // Show dialog
    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        dialog.show();
      }
    });
    assertThat(dialog.isShowing()).isTrue();
    assertThat(AutoDisposeAndroidUtil.isAttached(dialog.getWindow().getDecorView())).isFalse();

    subject.to(AutoDispose.with(DialogScopeProvider.from(dialog)).<Integer>forObservable())
        .subscribe(o);

    Disposable d = o.takeSubscribe();
    Throwable t = o.takeError();
    assertThat(t).isInstanceOf(IllegalStateException.class);
    assertThat(t.getMessage()).contains("main thread");
    o.assertNoMoreEvents();
    assertThat(d.isDisposed()).isTrue();
  }

  @Test public void observable_offBeforeAttach_shouldFail() {
    final RecordingObserver<Integer> o = new RecordingObserver<>(LOGGER);
    final PublishSubject<Integer> subject = PublishSubject.create();

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        subject.to(AutoDispose.with(DialogScopeProvider.from(dialog)).<Integer>forObservable())
            .subscribe(o);
      }
    });

    Disposable d = o.takeSubscribe();
    Throwable t = o.takeError();
    assertThat(t).isInstanceOf(OutsideLifecycleException.class);
    o.assertNoMoreEvents();
    assertThat(d.isDisposed()).isTrue();
  }

  @Test public void observable_offAfterDetach_shouldFail() {
    final RecordingObserver<Integer> o = new RecordingObserver<>(LOGGER);
    final PublishSubject<Integer> subject = PublishSubject.create();

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        dialog.show();
      }
    });

    assertThat(dialog.isShowing()).isTrue();
    assertThat(AutoDisposeAndroidUtil.isAttached(dialog.getWindow().getDecorView())).isFalse();

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        dialog.dismiss();
      }
    });

    assertThat(dialog.isShowing()).isFalse();
    assertThat(AutoDisposeAndroidUtil.isAttached(dialog.getWindow().getDecorView())).isFalse();

    instrumentation.runOnMainSync(new Runnable() {
      @Override public void run() {
        subject.to(AutoDispose.with(DialogScopeProvider.from(dialog)).<Integer>forObservable())
            .subscribe(o);
      }
    });

    Disposable d = o.takeSubscribe();
    Throwable t = o.takeError();
    assertThat(t).isInstanceOf(OutsideLifecycleException.class);
    o.assertNoMoreEvents();
    assertThat(d.isDisposed()).isTrue();
  }
}

AutoDisposeTestActivity.java

/*
 * Copyright (C) 2017. Uber Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.uber.autodispose.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;

@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/19
public final class AutoDisposeTestActivity extends Activity {
  FrameLayout parent;
  View child;
  Dialog dialog;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    parent = new FrameLayout(this);
    child = new View(this);
    setContentView(parent);

    dialog = new AlertDialog.Builder(this).setTitle("DialogScopeProviderTest").create();
  }
}

from autodispose.

ZacSweers avatar ZacSweers commented on April 28, 2024

you're welcome to use that code in your own project, but as mentioned above, decor view is not the correct approach for this. I'm going to close this out

from autodispose.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.