References to getCurrentActivity make it impossible to build an InputMethodService on Android
Asked Answered
G

0

10

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() {

    }
}
Genoa answered 24/6, 2016 at 2:41 Comment(3)
And do you have a log, or something?Dnieper
I don't have a log, but the Assert statement in the first code box fails: Assertions.assertNotNull(fragmentManagerHelper, "Attached DialogModule to host with pending alert but no FragmentManager (not attached to an Activity).");Genoa
The fragmentManagerHelper is null because it isn't running the the application context and so getFragmentManager() is returning null.Genoa

© 2022 - 2024 — McMap. All rights reserved.