Browse Source

android: better lifecycle, splash is the responsibility of the user

Daniel Zlotin 7 years ago
parent
commit
904fa36357

+ 0
- 12
android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java View File

@@ -1,22 +1,14 @@
1 1
 package com.reactnativenavigation;
2 2
 
3
-import android.os.Bundle;
4 3
 import android.support.annotation.Nullable;
5 4
 import android.support.v7.app.AppCompatActivity;
6 5
 import android.view.View;
7 6
 
8 7
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
9
-import com.reactnativenavigation.views.NavigationSplashView;
10 8
 
11 9
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
12 10
     private View contentView;
13 11
 
14
-    @Override
15
-    public void onCreate(Bundle savedInstanceState) {
16
-        super.onCreate(savedInstanceState);
17
-        setContentView(new NavigationSplashView(this));
18
-    }
19
-
20 12
     @Override
21 13
     public void setContentView(View contentView) {
22 14
         super.setContentView(contentView);
@@ -32,8 +24,4 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
32 24
     public void invokeDefaultOnBackPressed() {
33 25
         onBackPressed();
34 26
     }
35
-
36
-    public long splashMinimumDuration() {
37
-        return 1000;
38
-    }
39 27
 }

+ 107
- 116
android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivityLifecycleHandler.java View File

@@ -9,123 +9,114 @@ import com.facebook.react.bridge.ReactContext;
9 9
 import com.reactnativenavigation.NavigationActivity;
10 10
 import com.reactnativenavigation.react.NavigationEventEmitter;
11 11
 import com.reactnativenavigation.react.ReactDevPermission;
12
-import com.reactnativenavigation.utils.UiThread;
13
-import com.reactnativenavigation.views.NavigationSplashView;
14 12
 
15
-import java.util.concurrent.atomic.AtomicLong;
13
+import java.util.concurrent.atomic.AtomicBoolean;
16 14
 
17 15
 public class NavigationActivityLifecycleHandler implements Application.ActivityLifecycleCallbacks {
18
-    private interface OnContextCreated {
19
-        void onContextCreated(long timeElapsed);
20
-    }
21
-
22
-    private final ReactInstanceManager reactInstanceManager;
23
-
24
-    public NavigationActivityLifecycleHandler(ReactInstanceManager reactInstanceManager) {
25
-        this.reactInstanceManager = reactInstanceManager;
26
-    }
27
-
28
-    @Override
29
-    public void onActivityCreated(final Activity activity, Bundle bundle) {
30
-    }
31
-
32
-    @Override
33
-    public void onActivityStarted(Activity activity) {
34
-    }
35
-
36
-    @Override
37
-    public void onActivityResumed(Activity activity) {
38
-        if (activity instanceof NavigationActivity) {
39
-            handleResumed((NavigationActivity) activity);
40
-        }
41
-    }
42
-
43
-    @Override
44
-    public void onActivityPaused(Activity activity) {
45
-        if (activity instanceof NavigationActivity) {
46
-            handlePaused((NavigationActivity) activity);
47
-        }
48
-    }
49
-
50
-    @Override
51
-    public void onActivityStopped(Activity activity) {
52
-    }
53
-
54
-    @Override
55
-    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
56
-    }
57
-
58
-    @Override
59
-    public void onActivityDestroyed(Activity activity) {
60
-        if (activity instanceof NavigationActivity) {
61
-            handleDestroyed((NavigationActivity) activity);
62
-        }
63
-    }
64
-
65
-    private void handleResumed(NavigationActivity activity) {
66
-        if (ReactDevPermission.shouldAskPermission()) {
67
-            ReactDevPermission.askPermission(activity);
68
-        } else {
69
-            reactInstanceManager.onHostResume(activity, activity);
70
-            prepareReactApp(activity);
71
-        }
72
-    }
73
-
74
-    private void prepareReactApp(final NavigationActivity activity) {
75
-        if (shouldCreateContext()) {
76
-            createReactContext(new OnContextCreated() {
77
-                @Override
78
-                public void onContextCreated(long timeElapsed) {
79
-                    emitAppLaunchedAfterDelay(activity.splashMinimumDuration() - timeElapsed);
80
-                }
81
-            });
82
-        } else if (waitingForAppLaunchedEvent(activity)) {
83
-            emitAppLaunchedAfterDelay(activity.splashMinimumDuration());
84
-        }
85
-    }
86
-
87
-    /**
88
-     * @return true if we are a newly created activity, but react context already exists
89
-     */
90
-    private boolean waitingForAppLaunchedEvent(NavigationActivity activity) {
91
-        return activity.getContentView() instanceof NavigationSplashView;
92
-    }
93
-
94
-
95
-    private void createReactContext(final OnContextCreated onContextCreated) {
96
-        final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
97
-        reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
98
-            @Override
99
-            public void onReactContextInitialized(final ReactContext context) {
100
-                reactInstanceManager.removeReactInstanceEventListener(this);
101
-                onContextCreated.onContextCreated(System.currentTimeMillis() - startTime.get());
102
-            }
103
-        });
104
-        reactInstanceManager.createReactContextInBackground();
105
-    }
106
-
107
-    private boolean shouldCreateContext() {
108
-        return !reactInstanceManager.hasStartedCreatingInitialContext();
109
-    }
110
-
111
-    private void handlePaused(NavigationActivity activity) {
112
-        if (reactInstanceManager.hasStartedCreatingInitialContext()) {
113
-            reactInstanceManager.onHostPause(activity);
114
-        }
115
-    }
116
-
117
-    private void handleDestroyed(NavigationActivity activity) {
118
-        if (reactInstanceManager.hasStartedCreatingInitialContext()) {
119
-            reactInstanceManager.onHostDestroy(activity);
120
-        }
121
-    }
122
-
123
-    private void emitAppLaunchedAfterDelay(long delay) {
124
-        UiThread.postDelayed(new Runnable() {
125
-            @Override
126
-            public void run() {
127
-                new NavigationEventEmitter(reactInstanceManager.getCurrentReactContext()).emitAppLaunched();
128
-            }
129
-        }, delay);
130
-    }
16
+	private final ReactInstanceManager reactInstanceManager;
17
+	private final AtomicBoolean appLaunchEmitted = new AtomicBoolean(false);
18
+
19
+	public NavigationActivityLifecycleHandler(ReactInstanceManager reactInstanceManager) {
20
+		this.reactInstanceManager = reactInstanceManager;
21
+	}
22
+
23
+	@Override
24
+	public void onActivityCreated(final Activity activity, Bundle bundle) {
25
+		if (activity instanceof NavigationActivity) {
26
+			handleCreated();
27
+		}
28
+	}
29
+
30
+	@Override
31
+	public void onActivityStarted(Activity activity) {
32
+	}
33
+
34
+	@Override
35
+	public void onActivityResumed(Activity activity) {
36
+		if (activity instanceof NavigationActivity) {
37
+			handleResumed((NavigationActivity) activity);
38
+		}
39
+	}
40
+
41
+	@Override
42
+	public void onActivityPaused(Activity activity) {
43
+		if (activity instanceof NavigationActivity) {
44
+			handlePaused((NavigationActivity) activity);
45
+		}
46
+	}
47
+
48
+	@Override
49
+	public void onActivityStopped(Activity activity) {
50
+	}
51
+
52
+	@Override
53
+	public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
54
+	}
55
+
56
+	@Override
57
+	public void onActivityDestroyed(Activity activity) {
58
+		if (activity instanceof NavigationActivity) {
59
+			handleDestroyed((NavigationActivity) activity);
60
+		}
61
+	}
62
+
63
+	private void handleCreated() {
64
+		appLaunchEmitted.set(false);
65
+	}
66
+
67
+	private void handleResumed(NavigationActivity activity) {
68
+		if (ReactDevPermission.shouldAskPermission()) {
69
+			ReactDevPermission.askPermission(activity);
70
+		} else {
71
+			reactInstanceManager.onHostResume(activity, activity);
72
+			prepareReactApp();
73
+		}
74
+	}
75
+
76
+	private void prepareReactApp() {
77
+		if (shouldCreateContext()) {
78
+			appLaunchEmitted.set(false);
79
+			createReactContext(new Runnable() {
80
+				@Override
81
+				public void run() {
82
+					emitAppLaunchedOnceIfNeeded();
83
+				}
84
+			});
85
+		} else {
86
+			emitAppLaunchedOnceIfNeeded();
87
+		}
88
+	}
89
+
90
+	private void emitAppLaunchedOnceIfNeeded() {
91
+		if (appLaunchEmitted.compareAndSet(false, true)) {
92
+			NavigationEventEmitter.emit(reactInstanceManager.getCurrentReactContext()).appLaunched();
93
+		}
94
+	}
95
+
96
+	private void createReactContext(final Runnable onComplete) {
97
+		reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
98
+			@Override
99
+			public void onReactContextInitialized(final ReactContext context) {
100
+				reactInstanceManager.removeReactInstanceEventListener(this);
101
+				onComplete.run();
102
+			}
103
+		});
104
+		reactInstanceManager.createReactContextInBackground();
105
+	}
106
+
107
+	private boolean shouldCreateContext() {
108
+		return !reactInstanceManager.hasStartedCreatingInitialContext();
109
+	}
110
+
111
+	private void handlePaused(NavigationActivity activity) {
112
+		if (reactInstanceManager.hasStartedCreatingInitialContext()) {
113
+			reactInstanceManager.onHostPause(activity);
114
+		}
115
+	}
116
+
117
+	private void handleDestroyed(NavigationActivity activity) {
118
+		if (reactInstanceManager.hasStartedCreatingInitialContext()) {
119
+			reactInstanceManager.onHostDestroy(activity);
120
+		}
121
+	}
131 122
 }

+ 10
- 5
android/app/src/main/java/com/reactnativenavigation/react/NavigationEventEmitter.java View File

@@ -3,17 +3,22 @@ package com.reactnativenavigation.react;
3 3
 import com.facebook.react.bridge.Arguments;
4 4
 import com.facebook.react.bridge.ReactContext;
5 5
 import com.facebook.react.bridge.WritableMap;
6
-import com.facebook.react.modules.core.DeviceEventManagerModule;
6
+
7
+import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
7 8
 
8 9
 public class NavigationEventEmitter {
9 10
 
10
-    private final DeviceEventManagerModule.RCTDeviceEventEmitter emitter;
11
+    public static NavigationEventEmitter emit(ReactContext context) {
12
+        return new NavigationEventEmitter(context);
13
+    }
14
+
15
+    private final RCTDeviceEventEmitter emitter;
11 16
 
12
-    public NavigationEventEmitter(ReactContext reactContext) {
13
-        this.emitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
17
+    private NavigationEventEmitter(ReactContext reactContext) {
18
+        this.emitter = reactContext.getJSModule(RCTDeviceEventEmitter.class);
14 19
     }
15 20
 
16
-    public void emitAppLaunched() {
21
+    public void appLaunched() {
17 22
         emit("RNN.appLaunched");
18 23
     }
19 24
 

+ 0
- 17
android/app/src/main/java/com/reactnativenavigation/views/NavigationSplashView.java View File

@@ -1,17 +0,0 @@
1
-package com.reactnativenavigation.views;
2
-
3
-import android.content.Context;
4
-import android.widget.FrameLayout;
5
-import android.widget.ImageView;
6
-
7
-import com.reactnativenavigation.R;
8
-
9
-public class NavigationSplashView extends FrameLayout {
10
-    public NavigationSplashView(Context context) {
11
-        super(context);
12
-        ImageView image = new ImageView(context);
13
-        image.setImageResource(R.drawable.logo);
14
-        image.setContentDescription(getClass().getSimpleName());
15
-        addView(image);
16
-    }
17
-}

BIN
android/app/src/main/res/drawable-hdpi/ic_action_name.png View File


BIN
android/app/src/main/res/drawable-hdpi/search.png View File


BIN
android/app/src/main/res/drawable-mdpi/ic_action_name.png View File


BIN
android/app/src/main/res/drawable-mdpi/search.png View File


BIN
android/app/src/main/res/drawable-xhdpi/ic_action_name.png View File


BIN
android/app/src/main/res/drawable-xhdpi/search.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/ic_action_name.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/logo.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/search.png View File


+ 0
- 11
android/app/src/main/res/layout/new_root.xml View File

@@ -1,11 +0,0 @@
1
-<?xml version="1.0" encoding="utf-8"?>
2
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
-              android:layout_width="match_parent"
4
-              android:layout_height="match_parent"
5
-              android:orientation="vertical">
6
-    <TextView
7
-        android:layout_width="wrap_content"
8
-        android:layout_height="wrap_content"
9
-        android:layout_centerInParent="true"
10
-        android:text="new root :)"/>
11
-</RelativeLayout>

+ 0
- 11
android/app/src/main/res/layout/splash.xml View File

@@ -1,11 +0,0 @@
1
-<?xml version="1.0" encoding="utf-8"?>
2
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
-              android:layout_width="match_parent"
4
-              android:layout_height="match_parent"
5
-              android:orientation="vertical">
6
-    <TextView
7
-        android:layout_width="wrap_content"
8
-        android:layout_height="wrap_content"
9
-        android:layout_centerInParent="true"
10
-        android:text="Splash :)"/>
11
-</RelativeLayout>

+ 0
- 34
android/app/src/main/res/layout/tab_activity.xml View File

@@ -1,34 +0,0 @@
1
-<?xml version="1.0" encoding="utf-8"?>
2
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
-    xmlns:app="http://schemas.android.com/apk/res-auto"
4
-    xmlns:tools="http://schemas.android.com/tools"
5
-    android:layout_width="match_parent"
6
-    android:layout_height="match_parent"
7
-    android:orientation="vertical"
8
-    tools:context=".activities.TabActivity">
9
-
10
-    <android.support.design.widget.AppBarLayout
11
-        android:id="@+id/appbar"
12
-        android:layout_width="match_parent"
13
-        android:layout_height="wrap_content"
14
-        android:fitsSystemWindows="true">
15
-
16
-        <com.reactnativenavigation.views.RnnToolBar
17
-            android:id="@+id/toolbar"
18
-            android:layout_width="match_parent"
19
-            android:layout_height="?attr/actionBarSize"
20
-            app:layout_scrollFlags="scroll|enterAlways" />
21
-
22
-        <com.reactnativenavigation.views.RnnTabLayout
23
-            android:id="@+id/tabLayout"
24
-            android:layout_width="match_parent"
25
-            android:layout_height="?attr/actionBarSize"
26
-            app:layout_scrollFlags="scroll|enterAlways" />
27
-    </android.support.design.widget.AppBarLayout>
28
-
29
-    <android.support.v4.view.ViewPager
30
-        android:id="@+id/viewPager"
31
-        android:layout_width="match_parent"
32
-        android:layout_height="match_parent"
33
-        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
34
-</LinearLayout>

+ 0
- 10
android/app/src/main/res/menu/search_item.xml View File

@@ -1,10 +0,0 @@
1
-<?xml version="1.0" encoding="utf-8"?>
2
-<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android">
3
-    <item
4
-        android:id="@+id/toolbar_action_search"
5
-        android:title="Search"
6
-        android:icon="@drawable/search"
7
-        app:showAsAction="always|collapseActionView"
8
-        android:iconifiedByDefault="true"
9
-        app:actionViewClass="android.support.v7.widget.SearchView"/>
10
-</menu>

+ 0
- 4
android/app/src/main/res/menu/stub.xml View File

@@ -1,4 +0,0 @@
1
-<?xml version="1.0" encoding="utf-8"?>
2
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
-
4
-</menu>

+ 6
- 3
android/app/src/test/java/com/reactnativenavigation/NavigationActivityTest.java View File

@@ -1,6 +1,6 @@
1 1
 package com.reactnativenavigation;
2 2
 
3
-import com.reactnativenavigation.views.NavigationSplashView;
3
+import android.view.View;
4 4
 
5 5
 import org.junit.Test;
6 6
 import org.robolectric.Robolectric;
@@ -9,8 +9,11 @@ import static org.assertj.core.api.Java6Assertions.assertThat;
9 9
 
10 10
 public class NavigationActivityTest extends BaseTest {
11 11
     @Test
12
-    public void showsSplashView() throws Exception {
12
+    public void holdsContentView() throws Exception {
13 13
         NavigationActivity activity = Robolectric.setupActivity(NavigationActivity.class);
14
-        assertThat(activity.getContentView()).isInstanceOf(NavigationSplashView.class);
14
+        assertThat(activity.getContentView()).isNull();
15
+        View view = new View(activity);
16
+        activity.setContentView(view);
17
+        assertThat(activity.getContentView()).isSameAs(view);
15 18
     }
16 19
 }