https://github.com/facebook/react-native/issues/7762
I'm trying to build a custom keyboard with React-Native on Android. They are implemented with a InputMethodService and thus it is really hard to provide an rnplay for. I've tracked things down to the following code in the DialogModule.class file
public void onHostResume() {
this.mIsInForeground = true;
DialogModule.FragmentManagerHelper fragmentManagerHelper = this.getFragmentManagerHelper();
Assertions.assertNotNull(fragmentManagerHelper, "Attached DialogModule to host with pending alert but no FragmentManager (not attached to an Activity).");
fragmentManagerHelper.showPendingAlert();
}
@Nullable
private DialogModule.FragmentManagerHelper getFragmentManagerHelper() {
Activity activity = this.getCurrentActivity();
return activity == null?null:(activity instanceof FragmentActivity?new DialogModule.FragmentManagerHelper(((FragmentActivity)activity).getSupportFragmentManager()):new DialogModule.FragmentManagerHelper(activity.getFragmentManager()));
}
Here is how I'm setting up my SimpleIME
package com.customKeyboard;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import com.facebook.react.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
import com.github.yamill.orientation.OrientationPackage;
import java.util.Arrays;
import java.util.List;
public class SimpleIME extends InputMethodService
implements KeyboardView.OnKeyboardActionListener,DefaultHardwareBackBtnHandler {
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable ReactRootView mReactRootView;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
private KeyboardView kv;
private View v;
private Keyboard keyboard;
private boolean caps = false;
@Override
public View onCreateInputView() {
android.os.Debug.waitForDebugger();
try {
v = getLayoutInflater().inflate(R.layout.keyboard, null);
//kv = (KeyboardView) v.findViewById(R.id.keyboard);
//keyboard = new Keyboard(this, R.xml.qwerty);
//kv.setKeyboard(keyboard);
//kv.setOnKeyboardActionListener(this);
//InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//imm.
mReactInstanceManager = createReactInstanceManager();
mReactRootView = createRootView();
mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
//this.getWindow().setContentView(mReactRootView);
// insert into main view
ViewGroup insertPoint = (ViewGroup) v.findViewById(R.id.linview);
insertPoint.addView(mReactRootView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
v.invalidate();
}
catch (Exception e){
int z = 1;
int y = 2;
}
return v;
}
/**
* Returns the launchOptions which will be passed to the {@link ReactInstanceManager}
* when the application is started. By default, this will return null and an empty
* object will be passed to your top level component as its initial props.
* If your React Native application requires props set outside of JS, override
* this method to return the Android.os.Bundle of your desired initial props.
*/
protected @Nullable Bundle getLaunchOptions() {
Bundle b = new Bundle();
b.putString("mode", "keyboard");
return b;
}
/**
* A subclass may override this method if it needs to use a custom {@link ReactRootView}.
*/
protected ReactRootView createRootView() {
return new ReactRootView(this);
}
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setInitialLifecycleState(LifecycleState.RESUMED);
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(getBundleAssetName());
}
return builder.build();
}
/**
* Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
* from a custom path. By default it is loaded from Android assets, from a path specified
* by {@link getBundleAssetName}.
* e.g. "file://sdcard/myapp_cache/index.android.bundle"
*/
protected @Nullable String getJSBundleFile() {
return null;
}
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
};
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
//,new OrientationPackage()
);
}
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
protected String getJSMainModuleName() {
return "index.android";
}
protected String getMainComponentName() {
return "customKeyboard";
}
@Override
public void onPress(int primaryCode) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeDown() {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeUp() {
}
private void playClick(int keyCode){
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
switch(keyCode){
case 32:
am.playSoundEffect(AudioManager.FX_KEYPRESS_SPACEBAR);
break;
case Keyboard.KEYCODE_DONE:
case 10:
am.playSoundEffect(AudioManager.FX_KEYPRESS_RETURN);
break;
case Keyboard.KEYCODE_DELETE:
am.playSoundEffect(AudioManager.FX_KEYPRESS_DELETE);
break;
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
}
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection ic = getCurrentInputConnection();
playClick(primaryCode);
switch(primaryCode){
case Keyboard.KEYCODE_DELETE :
ic.deleteSurroundingText(1, 0);
break;
case Keyboard.KEYCODE_SHIFT:
caps = !caps;
keyboard.setShifted(caps);
kv.invalidateAllKeys();
break;
case Keyboard.KEYCODE_DONE:
ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
break;
default:
char code = (char)primaryCode;
if(Character.isLetter(code) && caps){
code = Character.toUpperCase(code);
}
ic.commitText(String.valueOf(code),1);
}
}
@Override
public void invokeDefaultOnBackPressed() {
}
}