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

References to getCurrentActivity make it impossible to build an InputMethodService on Android #7762

Closed
skilesare opened this issue May 25, 2016 · 8 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@skilesare
Copy link

skilesare commented May 25, 2016

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

    }
}
@skilesare
Copy link
Author

I've submitted this to Stack Overflow and put up a bounty for a solution or work around:

http://stackoverflow.com/questions/38004840/references-to-getcurrentactivity-make-it-impossible-to-build-an-inputmethodservi

@satya164
Copy link
Contributor

What's the problem you're facing? Are you getting an error message? A crash? Why would a reference to getCurrentActivity break your service?

@skilesare
Copy link
Author

The problem is that this code from https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/DialogModule.java always returns null.

private @Nullable FragmentManagerHelper getFragmentManagerHelper() {
    Activity activity = getCurrentActivity();
    if (activity == null) {
      return null;
    }
    if (activity instanceof FragmentActivity) {
      return new FragmentManagerHelper(((FragmentActivity) activity).getSupportFragmentManager());
    } else {
      return new FragmentManagerHelper(activity.getFragmentManager());
    }
  }

Because this always returns null: (from https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java)

@Nullable Activity getCurrentActivity() {
    if (mCurrentActivity == null) {
      return null;
    }
    return mCurrentActivity.get();
  }

So, when this code for the onHostResume runs it always fails the assert and stops running.

My React Native code is running inside of a keyboard so the 'CurrentActivity' is usually another application.

@skilesare
Copy link
Author

07-02 12:33:46.804 4901-4901/com.mtildakeyboard E/AndroidRuntime: FATAL EXCEPTION: main
                                                                  Process: com.mtildakeyboard, PID: 4901
                                                                  java.lang.AssertionError: Attached DialogModule to host with pending alert but no FragmentManager (not attached to an Activity).
                                                                      at com.facebook.infer.annotation.Assertions.assertNotNull(Assertions.java:28)
                                                                      at com.facebook.react.modules.dialog.DialogModule.onHostResume(DialogModule.java:201)
                                                                      at com.facebook.react.bridge.ReactContext.onHostResume(ReactContext.java:161)
                                                                      at com.facebook.react.ReactInstanceManagerImpl.moveToResumedLifecycleState(ReactInstanceManagerImpl.java:574)
                                                                      at com.facebook.react.ReactInstanceManagerImpl.moveReactContextToCurrentLifecycleState(ReactInstanceManagerImpl.java:932)
                                                                      at com.facebook.react.ReactInstanceManagerImpl.setupReactContext(ReactInstanceManagerImpl.java:743)
                                                                      at com.facebook.react.ReactInstanceManagerImpl.access$800(ReactInstanceManagerImpl.java:100)
                                                                      at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.onPostExecute(ReactInstanceManagerImpl.java:207)
                                                                      at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.onPostExecute(ReactInstanceManagerImpl.java:180)
                                                                      at android.os.AsyncTask.finish(AsyncTask.java:632)
                                                                      at android.os.AsyncTask.access$600(AsyncTask.java:177)
                                                                      at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
                                                                      at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                      at android.os.Looper.loop(Looper.java:136)
                                                                      at android.app.ActivityThread.main(ActivityThread.java:5017)
                                                                      at java.lang.reflect.Method.invokeNative(Native Method)
                                                                      at java.lang.reflect.Method.invoke(Method.java:515)
                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:611)
                                                                      at dalvik.system.NativeStart.main(Native Method)

@skilesare
Copy link
Author

Looks like this was added:


  public void onNewIntent(@Nullable Activity activity, Intent intent) {
    UiThreadUtil.assertOnUiThread();
    mCurrentActivity = new WeakReference(activity);
    for (ActivityEventListener listener : mActivityEventListeners) {
      listener.onNewIntent(intent);
    }
  }

in 2fc0f40

Perhaps this will fix my issue?

skilesare referenced this issue Jul 2, 2016
Reviewed By: foghina

Differential Revision: D3475896

fbshipit-source-id: d8e5d7734974132307a85d21e4c1602327a479fa
@mkonicek
Copy link
Contributor

Hi there! This issue is being closed because it has been inactive for a while.

But don't worry, it will live on with ProductPains! Check out its new home: https://productpains.com/post/react-native/references-to-getcurrentactivity-make-it-impossible-to-build-an-inputmethodservice-on-android

Product Pains has been very useful in highlighting the top bugs and feature requests:
https://productpains.com/product/react-native?tab=top

Also, if this issue is a bug, please consider sending a pull request with a fix.

@skilesare
Copy link
Author

Still waiting o a fix. Is there any intention of supporting android non-app services? If not I'll move on, but I already have this keyboard in the iOS app store and really wish I could push out an android version.

@Patzelly
Copy link

Any updates on this issue ?

@facebook facebook locked as resolved and limited conversation to collaborators Jul 19, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

6 participants