Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refactor native Android Java method to be called from React Native?

I currently have the following activity which is created when my app starts and is declared as an activity in my AndroidManifest.xml:

AndroidManifest.xml:

<activity android:name=".IncomingActivity"></activity>

IncomingActivity.java:

package com.xyz;

import android.os.Build;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.WindowManager;

public class IncomingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
            setShowWhenLocked(true);
            setTurnScreenOn(true);
        }
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

        setContentView(R.layout.activity_incoming);
    }
}

I now want to be able to use this functionality on demand from React Native. I have refactored it like the below so far but when this native method is called, I just get a blank white screen and I think the problem is this line: activity.setContentView(mReactRootView);

Updated class:

public class UnlockDevice extends ReactContextBaseJavaModule {

    @Override
    public String getName() {
        return "UnlockDevice";
    }

        private ReactContext mReactContext;
        private ReactRootView mReactRootView;

    public UnlockDevice(ReactApplicationContext reactContext) {
        super(reactContext);
                mReactContext = reactContext;
                mReactRootView = new ReactRootView(reactContext);
    }

    /* React Methods */
   @ReactMethod
    public void Unlock() {
                    Activity activity = mReactContext.getCurrentActivity();

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                    activity.setShowWhenLocked(true);
                    activity.setTurnScreenOn(true);
                }
                
                activity.getWindow().addFlags(
                        WindowManager.LayoutParams.FLAG_FULLSCREEN 
                        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

                activity.setContentView(mReactRootView);
    }
}

What is the correct way to be able to call this method to toggle the functionality but with the main activity instead of starting a new one?

like image 635
Mr. Robot Avatar asked Mar 16 '21 20:03

Mr. Robot


People also ask

How do you call native function from React Native?

In order to access your native module from JavaScript you need to first import NativeModules from React Native: import { NativeModules } from 'react-native'; You can then access the CalendarModule native module off of NativeModules .

Where is MainApplication Java in React Native?

This file exists under the android folder in your react-native application directory. The path to this file is: android/app/src/main/java/com/your-app-name/MainApplication. java .


1 Answers

Explanation

Basically, you missed 2 things - you didn't register your UnlockDevice in a ReactPackage, and you didn't add your ReactPackage to the list of packages in the application.

Also, don't forget that your native method will not be executed on the UI thread with React. So, it's your responsibility to run it there. For example, by using runOnUiThread() as below:

...
    @ReactMethod
    public void Unlock() {
        Activity activity = mReactContext.getCurrentActivity();

        activity.runOnUiThread(() -> {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                activity.setShowWhenLocked(true);
                activity.setTurnScreenOn(true);
            }

            activity.getWindow().addFlags(
                WindowManager.LayoutParams.FLAG_FULLSCREEN
                        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
            activity.setContentView(mReactRootView);
        });
   }
...

So, first create a ReactPackage class to register your module.

MainReactPackage.java

public class MainReactPackage implements ReactPackage {
   @Override
   @NonNull
   public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
       return Collections.emptyList();
   }

   @Override
   @NonNull
   public List<NativeModule> createNativeModules(
           @NonNull ReactApplicationContext reactContext) {
       List<NativeModule> modules = new ArrayList<>();

       modules.add(new UnlockDevice(reactContext));

       return modules;
   }
}

Then, add the latter to the list of packages in your Application class.

YourApplication.java

 ...

 private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          packages.add(new MainReactPackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }
...

Full code

For a reference, I created a sample project similar to yours that works just fine. Below is relevant code.

MainActivity.java

package com.reacttest;

import android.os.Bundle;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript. This is used to schedule
     * rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "ReactTest";
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
    }
}

MainActivityModule

package com.reacttest;

import android.app.Activity;
import android.os.Build;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class MainActivityModule extends ReactContextBaseJavaModule {

    private ReactContext mReactContext;
    private ReactRootView mReactRootView;

    public MainActivityModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mReactContext = reactContext;
        mReactRootView = new ReactRootView(reactContext);
    }

    @ReactMethod
    public void helloFromAndroid() {
        Activity activity = mReactContext.getCurrentActivity();
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                    activity.setShowWhenLocked(true);
                    activity.setTurnScreenOn(true);
                }

                activity.getWindow().addFlags(
                        WindowManager.LayoutParams.FLAG_FULLSCREEN
                                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
                activity.setContentView(mReactRootView);
                Toast.makeText(activity, "Hello from Android", Toast.LENGTH_LONG).show();
            }
        });
    }

    @NonNull
    @Override
    public String getName() {
        return "MainActivityModule";
    }
}

MainActivityPackage.java

package com.reacttest;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class MainReactPackage implements ReactPackage {
    @Override
    @NonNull
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    @NonNull
    public List<NativeModule> createNativeModules(
            @NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new MainActivityModule(reactContext));

        return modules;
    }
}

MainApplication.java

package com.reacttest;

import android.app.Application;
import android.content.Context;

import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost =
            new ReactNativeHost(this) {
                @Override
                public boolean getUseDeveloperSupport() {
                    return BuildConfig.DEBUG;
                }

                @Override
                protected List<ReactPackage> getPackages() {
                    @SuppressWarnings("UnnecessaryLocalVariable")
                    List<ReactPackage> packages = new PackageList(this).getPackages();
                    packages.add(new MainReactPackage());
                    return packages;
                }

                @Override
                protected String getJSMainModuleName() {
                    return "index";
                }
            };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
        initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
    }

    /**
     * Loads Flipper in React Native templates. Call this in the onCreate method with something like
     * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
     *
     * @param context
     * @param reactInstanceManager
     */
    private static void initializeFlipper(
            Context context, ReactInstanceManager reactInstanceManager) {
        if (BuildConfig.DEBUG) {
            try {
        /*
         We use reflection here to pick up the class that initializes Flipper,
        since Flipper library is not available in release mode
        */
                Class<?> aClass = Class.forName("com.reacttest.ReactNativeFlipper");
                aClass
                        .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
                        .invoke(null, context, reactInstanceManager);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

App.js

import {NativeModules} from 'react-native';

var mainActivityModule = NativeModules.MainActivityModule;
mainActivityModule.unlockScreen();

Result

enter image description here

As you can see, the Toast from the Activity is shown. That's it!

like image 147
Anatolii Avatar answered Sep 21 '22 09:09

Anatolii