Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Allow to start a native fragment via Navigator.startNative(). #129

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.airbnb.android.react.navigation.ScreenCoordinatorLayout;
import com.airbnb.android.react.navigation.ReactAwareActivity;
import com.airbnb.android.react.navigation.ScreenCoordinator;
import com.airbnb.android.react.navigation.ScreenCoordinatorComponent;
import com.airbnb.android.react.navigation.ScreenCoordinatorLayout;

public class MainActivity extends ReactAwareActivity implements ScreenCoordinatorComponent {

private static final String TAG = MainActivity.class.getSimpleName();

private ScreenCoordinator screenCoordinator;
Expand All @@ -19,12 +20,25 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
ScreenCoordinatorLayout container = (ScreenCoordinatorLayout) findViewById(R.id.content);
screenCoordinator = new ScreenCoordinator(this, container, savedInstanceState);
screenCoordinator.registerScreen("NativeFragment2", Native2Fragment.FACTORY);

if (savedInstanceState == null) {
screenCoordinator.presentScreen(MainFragment.newInstance());
}
}

@Override
protected void onResume() {
super.onResume();
screenCoordinator.onResume();
}

@Override
protected void onPause() {
screenCoordinator.onPause();
super.onPause();
}

@Override
public ScreenCoordinator getScreenCoordinator() {
return screenCoordinator;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.airbnb.android.react.navigation.example;

import android.app.Application;

import com.airbnb.android.react.navigation.NativeNavigationPackage;
import com.airbnb.android.react.navigation.ReactNavigationCoordinator;
import com.facebook.react.ReactApplication;
Expand All @@ -22,7 +23,7 @@ public boolean getUseDeveloperSupport() {

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
return Arrays.asList(
new MainReactPackage(),
new NativeNavigationPackage()
);
Expand All @@ -48,5 +49,4 @@ public void onCreate() {
coordinator.injectReactInstanceManager(mReactNativeHost.getReactInstanceManager());
coordinator.start(this);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.airbnb.android.react.navigation.example;


import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.airbnb.android.react.navigation.NativeScreenFactory;

public class Native2Fragment extends Fragment {
static final NativeScreenFactory FACTORY = new NativeScreenFactory() {
@NonNull
@Override
public Fragment newScreen(@Nullable Bundle props) {
return new Native2Fragment();
}
};

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_native_2, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
toolbar.setTitle("Native Fragment");
toolbar.setNavigationIcon(R.drawable.n2_ic_arrow_back_white);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().onBackPressed();
}
});
}
}
23 changes: 23 additions & 0 deletions example/android/app/src/main/res/layout/fragment_native_2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_primary"
android:minHeight="?attr/actionBarSize"
app:theme="@style/ToolbarTheme" />

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="Second Native Fragment" />
</LinearLayout>
4 changes: 4 additions & 0 deletions example/screens/NavigationExampleScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export default class NavigationExampleScreen extends Component {
title="Navigation bar customisation"
onPress={() => Navigator.push('NavigationBar')}
/>
<Row
title="Start Native Screen 2"
onPress={() => Navigator.push('NativeFragment2')}
/>
</Screen>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.airbnb.android.react.navigation;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;

public interface NativeScreenFactory {
/**
* Creates a new {@linkplain Fragment native screen} with {@code props}.
*/
@NonNull
Fragment newScreen(@Nullable Bundle props);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import java.util.Map;

import static com.airbnb.android.react.navigation.ReactNativeIntents.EXTRA_IS_DISMISS;
import static com.airbnb.android.react.navigation.ScreenCoordinator.EXTRA_PAYLOAD;
import static com.airbnb.android.react.navigation.ReactNativeUtils.VERSION_CONSTANT_KEY;
import static com.airbnb.android.react.navigation.ScreenCoordinator.EXTRA_PAYLOAD;

class NavigatorModule extends ReactContextBaseJavaModule {
private static final int VERSION = 2;
Expand Down Expand Up @@ -113,8 +113,13 @@ public void pushNative(String name, ReadableMap props, ReadableMap options, Prom
if (activity == null) {
return;
}
Intent intent = coordinator.intentForKey(activity.getBaseContext(), name, props);
startActivityWithPromise(activity, intent, promise, options);

boolean startedFragment = coordinator.startFragmentForKey(name, props, options);

if (!startedFragment) {
Intent intent = coordinator.intentForKey(activity.getBaseContext(), name, props);
startActivityWithPromise(activity, intent, promise, options);
}
}

@SuppressWarnings("UnusedParameters")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
Expand Down Expand Up @@ -49,6 +52,7 @@ public class ReactNavigationCoordinator {
private boolean isSuccessfullyInitialized = false;
private static final int APP_INITIALIZE_TOAST_DELAY = 3000;

@Nullable ScreenCoordinator screenCoordinator;

private ReactNavigationCoordinator() {
}
Expand All @@ -71,6 +75,7 @@ public void onReactContextInitialized(ReactContext context) {
}
});
}

public void injectImplementation(NavigationImplementation implementation) {
if (this.navigationImplementation != null) {
// TODO: throw error. can only initialize once.
Expand Down Expand Up @@ -125,12 +130,17 @@ public void unregisterComponent(String name) {
*
* @see ReactExposedActivityParams#toIntent(Context, ReadableMap)
*/
Intent intentForKey(Context context, String key, ReadableMap arguments) {
@NonNull Intent intentForKey(Context context, String key, ReadableMap arguments) {
if (exposedActivities == null) {
throw new IllegalArgumentException("No Activities registered.");
}

for (ReactExposedActivityParams exposedActivity : exposedActivities) {
if (exposedActivity.key().equals(key)) {
return exposedActivity.toIntent(context, arguments);
}
}

throw new IllegalArgumentException(
String.format("Tried to push Activity with key '%s', but it could not be found", key));
}
Expand Down Expand Up @@ -202,4 +212,11 @@ public void run() {
}
}, APP_INITIALIZE_TOAST_DELAY);
}

boolean startFragmentForKey(String name, ReadableMap props, ReadableMap options) {
if (screenCoordinator == null) {
throw new IllegalStateException("screenCoordinator == null");
}
return screenCoordinator.pushNativeScreen(name, ConversionUtil.toBundle(props), ConversionUtil.toBundle(options));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.AnimRes;
import android.support.annotation.CallSuper;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
Expand All @@ -24,6 +26,7 @@
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;

Expand All @@ -34,9 +37,10 @@
*/
public class ScreenCoordinator {
private static final String TAG = ScreenCoordinator.class.getSimpleName();
static final String EXTRA_PAYLOAD = "payload";
private static final String TRANSITION_GROUP = "transitionGroup";

static final String EXTRA_PAYLOAD = "payload";

enum PresentAnimation {
Modal(R.anim.slide_up, R.anim.delay, R.anim.delay, R.anim.slide_down),
Push(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right),
Expand All @@ -61,6 +65,7 @@ enum PresentAnimation {
private ReactNavigationCoordinator reactNavigationCoordinator = ReactNavigationCoordinator.sharedInstance;

private int stackId = 0;

/**
* When we dismiss a back stack, the fragment manager would normally execute the latest fragment's
* pop exit animation. However, if we present A as a modal, push, B, then dismiss(), the latest
Expand All @@ -69,6 +74,8 @@ enum PresentAnimation {
*/
@AnimRes private int nextPopExitAnim;

private Map<String, NativeScreenFactory> factories = new LinkedHashMap<>();

public ScreenCoordinator(AppCompatActivity activity, ScreenCoordinatorLayout container,
@Nullable Bundle savedInstanceState) {
this.activity = activity;
Expand All @@ -81,6 +88,38 @@ void onSaveInstanceState(Bundle outState) {
// TODO
}

@CallSuper
public void onResume() {
reactNavigationCoordinator.screenCoordinator = this;
}

@CallSuper
public void onPause() {
reactNavigationCoordinator.screenCoordinator = null;
}

/**
* Register a {@linkplain NativeScreenFactory} for {@code moduleName}.
*/
public void registerScreen(String moduleName, @NonNull NativeScreenFactory nativeScreenFactory) {
factories.put(moduleName, nativeScreenFactory);
}

/**
* Will try to push a native screen if a {@link NativeScreenFactory factory} is available for {@code moduleName}.
* Will return {@code true} if a screen was pushed, otherwise false.
*/
@CheckResult
boolean pushNativeScreen(String moduleName, @Nullable Bundle props, @Nullable Bundle options) {
NativeScreenFactory nativeScreenFactory = factories.get(moduleName);
if (nativeScreenFactory != null) {
pushScreen(nativeScreenFactory.newScreen(props), options);
return true;
}

return false;
}

public void pushScreen(String moduleName) {
pushScreen(moduleName, null, null);
}
Expand Down