I'm using intent to start my React-Native app, and I'm trying to find out how to get the variables I put on my intent in the react native code. Is this possible from within react-native or do I have to write some java code to get it?
the code I use to start the app :
Intent intent = new Intent(this, MainActivity.class);
Intent.putExtra("alarm",true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Thanks!
Try this to get Intent params at react-native app.
In my native App, I use this code:
Intent launchIntent = getPackageManager().getLaunchIntentForPackage("com.my.react.app.package");
launchIntent.putExtra("test", "12331");
startActivity(launchIntent);
In react-native project, my MainActivity.java
public class MainActivity extends ReactActivity {
#Override
protected String getMainComponentName() {
return "FV";
}
public static class TestActivityDelegate extends ReactActivityDelegate {
private static final String TEST = "test";
private Bundle mInitialProps = null;
private final
#Nullable
Activity mActivity;
public TestActivityDelegate(Activity activity, String mainComponentName) {
super(activity, mainComponentName);
this.mActivity = activity;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
Bundle bundle = mActivity.getIntent().getExtras();
if (bundle != null && bundle.containsKey(TEST)) {
mInitialProps = new Bundle();
mInitialProps.putString(TEST, bundle.getString(TEST));
}
super.onCreate(savedInstanceState);
}
#Override
protected Bundle getLaunchOptions() {
return mInitialProps;
}
}
#Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new TestActivityDelegate(this, getMainComponentName());
}
}
In my first Container I get the param in this.props
export default class App extends Component {
render() {
console.log('App props', this.props);
//...
}
}
The complete example I found here:
http://cmichel.io/how-to-set-initial-props-in-react-native/
You can pass initial props as a bundle to the third parameter in the startReactApplication method, like so:
Bundle initialProps = new Bundle();
initialProps.putString("alarm", true);
mReactRootView.startReactApplication( mReactInstanceManager, "HelloWorld", initialProps );
See this answer for more details: https://stackoverflow.com/a/34226172/293280
I think this more correct way based on answer of #Eduardo Junior
class MainDelegate(activity: ReactActivity, mainComponentName: String?) :
ReactActivityDelegate(activity, mainComponentName) {
private var params: Bundle? = null
override fun onCreate(savedInstanceState: Bundle?) {
params = plainActivity.intent.extras
super.onCreate(savedInstanceState)
}
override fun onNewIntent(intent: Intent?): Boolean {
params = intent?.extras
return super.onNewIntent(intent)
}
override fun getLaunchOptions() = params
}
class MainActivity : ReactActivity() {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName() = "example"
override fun createReactActivityDelegate(): ReactActivityDelegate {
return MainDelegate(this, mainComponentName)
}
}
Related
I have splash screen without a layout file. This is what I have tried:
public class SplashActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isNotAuthenticated()) {
openLoginInActivity();
} else {
openMainActivity();
}
finish();
}
private void openMainActivity() {
viewModel.idLiveData.observe(this, new Observer<String>() {
#Override
public void onChanged(String id) {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra("id", id);
startActivity(intent); //Go to MainActivity
}
});
}
}
Using this code, I get this error:
2019-09-03 12:03:06.615 1871-1934/? E/ViewRootImpl[myapp]: Could not unlock surface
java.lang.IllegalArgumentException
at android.view.Surface.nativeUnlockCanvasAndPost(Native Method)
at android.view.Surface.unlockSwCanvasAndPost(Surface.java:382)
at android.view.Surface.unlockCanvasAndPost(Surface.java:363)
at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:3451)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3339)
If i get the creation if the intent outside onChanged(), everything works fine. I have added a log statement and onChanged is not even triggered. So how can I move to the next activity without that error?
Edit:
public class SplashViewModel extends ViewModel {
private SplashRepository splashRepository;
MutableLiveData<String> idLiveData;
#Inject
SplashViewModel(SplashRepository splashRepository) {
this.splashRepository = splashRepository;
idLiveData = splashRepository.addIdToLiveData();
}
}
How I used live data in Splash Activity
class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override val mViewModel: SplashViewModel by currentScope.inject()
override fun getLayoutResId(): Int {
return R.layout.activity_splash
}
override fun initialization() {
}
override fun initializeObserver() {
mViewModel.liveData.observe(this, Observer {
if (it) {
launchActivityWithFinish<LoginActivity>()
}
})
}}
Here is My SplashViewModel
class SplashViewModel : BaseViewModel() {
val liveData = MutableLiveData<Boolean>()
init {
viewModelScope.launch {
delay(SPLASH_TIME_OUT)
liveData.postValue(true)
}
}}
My SplashActivity In java
public class SplashActivity1 extends BaseActivity<ActivitySplashBinding, SplashViewModel> {
#NotNull
#Override
protected SplashViewModel getMViewModel() {
return new SplashViewModel();
}
#Override
public int getLayoutResId() {
return R.layout.activity_splash;
}
#Override
public void initialization() {
}
#Override
public void initializeObserver() {
getMViewModel().getLiveData().observe(this, aBoolean -> {
if (aBoolean) {
Intent intent = new Intent(SplashActivity1.this, MainActivity.class);
intent.putExtra("id", "id");
startActivity(intent); //Go to MainActivity
finish();
}
});
}}
My SplashViewModel In Java
public class SplashViewModel extends BaseViewModel {
public MutableLiveData<Boolean> liveData = new MutableLiveData<>(false);
public SplashViewModel(){
new Handler().postDelayed(() -> liveData.postValue(true),SPLASH_TIME_OUT);
}}
It perfectly works for me in Kotlin & Java both
I had similar problems with navigation in the past. I usually fixed them by verifying in onChanged that the parameter (in your case the String id) is not null and inside call a view model method that changes the id to null.
viewModel.idLiveData.observe(this, new Observer<User>() {
#Override
public void onChanged(String id) {
if(id != null) {
viewModel.navigationDone();
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra("id", id);
startActivity(intent); //Go to MainActivity
}
}
});
where navigationDone is a view model method like:
void onNavigationDone() {
idLiveData.setValue(null);
}
friends,
I am working on Kotlin and trying to fetch the received SMS in the edit text.I don't know how to implement it. If anybody has the correct Kotlin code to fetch OTP please post it below however I have just implemented as below and stacked in the implementation so please post below Kotlin code if anybody has it
class SmsReceiver: BroadcastReceiver() {
private val mListener: SmsListener? = null
var b: Boolean? = null
var abcd: String? = null
var xyz:String? = null
#Suppress("DEPRECATION")
override fun onReceive(context: Context?, intent: Intent?) {
//val bundle = intent!!.getExtras()
val data = intent!!.extras
val pdus = data.get("pdus") as Array<Any>
}
}
below is the activity
public class OtpActivity extends Activity
{
EditText ed;
TextView tv;
String otp_generated,contactNo,id1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.otp);
ed=(EditText)findViewById(R.id.otp_et);
tv=(TextView) findViewById(R.id.verify_otp);
SmsReceiver.bin(new SmsListener() {
#Override
public void messageReceived(String messageText) {
ed.setText(messageText);
}
});
}
}
This can be done with RxJava:
public static Observable<String> createMessageMonitor(#NonNull final Context context) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
return fromIntentFilter(context, intentFilter)
.filter(intent -> intent != null && intent.getExtras() != null)
.map(intent -> {
Bundle bundle = intent.getExtras();
Object[] pdu_Objects = (Object[]) bundle.get("pdus");
if (pdu_Objects != null) {
for (Object aObject : pdu_Objects) {
final SmsMessage currentSMS = getIncomingMessage(aObject, bundle);
final String senderNumber = currentSMS.getDisplayOriginatingAddress();
final String message = currentSMS.getDisplayMessageBody().trim();
Matcher matcher = OTP_PATTERN.matcher(message);
if (matcher.matches()) {
return matcher.group(1);
}
}
}
return "";
}).filter(message -> !message.isEmpty());
}
Your OTP_PATTERN:
private static Pattern OTP_PATTERN = Pattern.compile("^(\\d{4}) is the OTP for your App.$");
Then subscribe the stream:
RxBroadcastReceivers2.createMessageMonitor(this)
.subscribeWith(this,
onNext = { /*Your OTP is here*/ },
onError = {
Timber.e(it, "Registration permission request failed")
}
)
NOTE: You need to get Manifest.permission.READ_SMS before using createMessageMonitor method.
If you don't want to write a receiver, you can use a simple lightweight library https://github.com/VitaliBov/SmsInterceptor
The library has 100% compatibility with Kotlin
You only need to override the interface method, create an Interceptor class and bind it with the life cycle. It looks like this:
public class AuthActivity extends AppCompatActivity implements OnMessageListener {
private SmsInterceptor smsInterceptor;
private EditText etAuthPassword;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
initViews();
initSmsInterceptor();
}
#Override
protected void onResume() {
super.onResume();
smsInterceptor.register();
}
#Override
protected void onPause() {
super.onPause();
smsInterceptor.unregister();
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
smsInterceptor.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
#Override
public void messageReceived(String message) {
// You can perform your validation here
etAuthPassword.setText(message);
}
private void initViews() {
etAuthPassword = findViewById(R.id.etAuthPassword);
etAuthPassword.addTextChangedListener(new SmsTextWatcher() {
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (charSequence.length() == 4) {
btnAuthPassword.setEnabled(true);
checkCode();
} else {
btnAuthPassword.setEnabled(false);
}
}
});
}
private void initSmsInterceptor() {
smsInterceptor = new SmsInterceptor(this, this);
// Not necessary
smsInterceptor.setRegex(SMS_CODE_REGEX);
smsInterceptor.setPhoneNumber(PHONE_NUMBER);
}
private void checkCode() {
// Validation
if (isValid) {
navigateToMainScreen();
}
}
}
I am trying to apply some transition animation when activities switch. However, the content of the next activity always displays only after the animation is completed, which causes a white flash when switching to the next activity.
This problem does not exist if I use a Android layout to be the content of the next Activity, so it's obvious a problem due to React-Native.
The following is the RouterModule. When JS code need to switch to another activity, it just call the push method and this module will call startActivity() and overridePendingTransition() to customize the animation:
RouterModule
public class RouterModule extends ReactContextBaseJavaModule {
public RouterModule(ReactApplicationContext reactContext) {
super(reactContext);
}
#Override
public String getName() {
return "RouterModule";
}
#Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<String, Object>();
return constants;
}
#ReactMethod
public void push(String uri, #Nullable ReadableMap config, String transition) {
Uri intentData = Uri.parse(uri);
Intent pageIntent = RedirectActivity.getPageIntentFromUri(intentData);
MyReactNativeApplication.CURRENT_VIEW_CONTEXT.startActivity(pageIntent);
switch(transition){
case "leftToRight":
MyReactNativeApplication.CURRENT_VIEW_CONTEXT.overridePendingTransition(R.anim.right_to_left_in, R.anim.right_to_left_out);
break;
default:
MyReactNativeApplication.CURRENT_VIEW_CONTEXT.overridePendingTransition(0, 0);
break;
}
}
#ReactMethod
public void pop(#Nullable ReadableMap config, String transition) {
final AppCompatActivity currentContext = CoinReactNativeApplication.CURRENT_VIEW_CONTEXT;
currentContext.finish();
switch(transition){
case "rightToLeft":
currentContext.overridePendingTransition(R.anim.left_to_right_in, R.anim.left_to_right_out);
break;
default:
break;
}
}
}
The following is the PageActivity. Every page used by React-Native JS code is actually rendered on this activity.
PageActivity
public class PageActivity extends AppCompatActivity {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
private ReactContext mReactContext;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
Bundle config = parseIntent(intent);
String uriString = (String) config.get("uriString");
Bundle initProps = (Bundle) config.get("config");
NavigatorHelper.handleNavbar(this, config);
render(uriString, config, initProps);
}
private void render(String uriString, Bundle config, Bundle initProps) {
mReactInstanceManager = MyReactNativeApplication.getApplication().getReactInstanceManager();
mReactRootView = new ReactRootView(this);
mReactRootView.startReactApplication(mReactInstanceManager, uriString, initProps);
setContentView(mReactRootView);
mReactContext = mReactInstanceManager.getCurrentReactContext();
}
}
And the following gif is the actual screenshot from my app. I lengthened the duration of the animation intentionally. You can see that the content (the list) will not be shown until the animation is completed.
How to make the content to be shown at the begnning of the animation? Just like those in a native Android app?
I have an Espresso test suite for UI tests that looks like this:
#RunWith(AndroidJUnit4.class)
public class SpecialUiTests {
#Rule
public final ActivityTestRule<SpecialActivity> activity
= new ActivityTestRule<>(SpecialActivity.class);
#Test
public void specialTest() {
...
}
...
}
The problem is, that activity expects a bundle, and crashes when it can't find the value it expects
public class SpecialActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
final String specialValue = getIntent().getBundleExtra(ARG_SPECIAL_BUNDLE)
.getString(KEY_SPECIAL_VALUE);
//Do something with specialValue <--- Crash
}
...
}
Can I set up a test rule and still pass the parameter (a bundle) the activity expects?
#Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
SpecialActivity.class,
true, // initialTouchMode
false); //Lazy launching
#Test
public void specialTest() {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString(SpecialActivity.KEY_SPECIAL_VALUE, "789");
intent.putExtra(SpecialActivity.ARG_SPECIAL_BUNDLE, bundle);
activityRule.launchActivity(intent);
onView(withId(R.id.special))
.check(matches(withText("789")));
}
Source: http://blog.sqisland.com/2015/04/espresso-21-activitytestrule.html
You can also override getActivityIntent() of your ActivityTestRule to create the Intent. This way, an Activity with the appropriate Intent is started automatically for all of your test methods. Sample:
#Rule
public ActivityTestRule<SpecialActivity> mActivity = new ActivityTestRule<SpecialActivity>(SpecialActivity.class) {
#Override
protected Intent getActivityIntent() {
final Context targetContext = InstrumentationRegistry.getTargetContext();
final Intent intent = new Intent(targetContext, SpecialActivity.class);
intent.putExtra("arg_one", 1);
return intent;
}
};
In case of a test that crosses multiple activities, is there a way to get current activity?
getActivtiy() method just gives one activity that was used to start the test.
I tried something like below,
public Activity getCurrentActivity() {
Activity activity = null;
ActivityManager am = (ActivityManager) this.getActivity().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
try {
Class<?> myClass = taskInfo.get(0).topActivity.getClass();
activity = (Activity) myClass.newInstance();
}
catch (Exception e) {
}
return activity;
}
but I get null object.
In Espresso, you can use ActivityLifecycleMonitorRegistry but it is not officially supported, so it may not work in future versions.
Here is how it works:
Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
final Activity[] activity = new Activity[1];
runTestOnUiThread(new Runnable() {
#Override
public void run() {
java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = Iterables.getOnlyElement(activities);
}});
return activity[0];
}
If all you need is to make the check against current Activity, use may get along with native Espresso one-liner to check that expected intent was launched:
intended(hasComponent(new ComponentName(getTargetContext(), ExpectedActivity.class)));
Espresso will also show you the intents fired in the meanwhile if not matching yours.
The only setup you need is to replace ActivityTestRule with IntentsTestRule in the test to let it keep track of the intents launching. And make sure this library is in your build.gradle dependencies:
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
I like #Ryan's version as it doesn't use undocumented internals, but you can write this even shorter:
private Activity getCurrentActivity() {
final Activity[] activity = new Activity[1];
onView(isRoot()).check(new ViewAssertion() {
#Override
public void check(View view, NoMatchingViewException noViewFoundException) {
activity[0] = (Activity) view.getContext();
}
});
return activity[0];
}
Please be aware, though that this will not work when running your tests in Firebase Test Lab. That fails with
java.lang.ClassCastException: com.android.internal.policy.DecorContext cannot be cast to android.app.Activity
The Android team has replaced ActivityTestRule with ActivityScenario. We could do activityTestRule.getActivity() with ActivityTestRule but not with ActivityScenario. Here is my work around solution for getting an Activity from ActivityScenario (inspired by #Ryan and #Fabian solutions)
#get:Rule
var activityRule = ActivityScenarioRule(MainActivity::class.java)
...
private fun getActivity(): Activity? {
var activity: Activity? = null
activityRule.scenario.onActivity {
activity = it
}
return activity
}
I couldn't get any of the other solutions to work, so I ended up having to do this:
Declare your ActivityTestRule:
#Rule
public ActivityTestRule<MainActivity> mainActivityTestRule =
new ActivityTestRule<>(MainActivity.class);
Declare a final Activity array to store your activities:
private final Activity[] currentActivity = new Activity[1];
Add a helper method to register with the application context to get lifecycle updates:
private void monitorCurrentActivity() {
mainActivityTestRule.getActivity().getApplication()
.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(final Activity activity, final Bundle savedInstanceState) { }
#Override
public void onActivityStarted(final Activity activity) { }
#Override
public void onActivityResumed(final Activity activity) {
currentActivity[0] = activity;
}
#Override
public void onActivityPaused(final Activity activity) { }
#Override
public void onActivityStopped(final Activity activity) { }
#Override
public void onActivitySaveInstanceState(final Activity activity, final Bundle outState) { }
#Override
public void onActivityDestroyed(final Activity activity) { }
});
}
Add a helper method to get the current activity
private Activity getCurrentActivity() {
return currentActivity[0];
}
So, once you've launched your first activity, just call monitorCurrentActivity() and then whenever you need a reference to the current activity you just call getCurrentActivity()
public static Activity getActivity() {
final Activity[] currentActivity = new Activity[1];
Espresso.onView(AllOf.allOf(ViewMatchers.withId(android.R.id.content), isDisplayed())).perform(new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isAssignableFrom(View.class);
}
#Override
public String getDescription() {
return "getting text from a TextView";
}
#Override
public void perform(UiController uiController, View view) {
if (view.getContext() instanceof Activity) {
Activity activity1 = ((Activity)view.getContext());
currentActivity[0] = activity1;
}
}
});
return currentActivity[0];
}
I improved #Fabian Streitel answer so you can use this method without ClassCastException
public static Activity getCurrentActivity() {
final Activity[] activity = new Activity[1];
onView(isRoot()).check((view, noViewFoundException) -> {
View checkedView = view;
while (checkedView instanceof ViewGroup && ((ViewGroup) checkedView).getChildCount() > 0) {
checkedView = ((ViewGroup) checkedView).getChildAt(0);
if (checkedView.getContext() instanceof Activity) {
activity[0] = (Activity) checkedView.getContext();
return;
}
}
});
return activity[0];
}
Based on https://stackoverflow.com/a/50762439/6007104 here's a Kotlin version of a generic util for accessing current Activity:
class CurrentActivityDelegate(application: Application) {
private var cachedActivity: Activity? = null
init {
monitorCurrentActivity(application)
}
fun getCurrentActivity() = cachedActivity
private fun monitorCurrentActivity(application: Application) {
application.registerActivityLifecycleCallbacks(
object : Application.ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
cachedActivity = activity
Log.i(TAG, "Current activity updated: ${activity::class.simpleName}")
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity?) {}
override fun onActivityPaused(activity: Activity?) {}
override fun onActivityStopped(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
override fun onActivityDestroyed(activity: Activity?) {}
})
}
}
And then simply use like so:
#Before
fun setup() {
currentActivityDelegate = CurrentActivityDelegate(activityTestRule.activity.application)
}
If you have the only Activity in your test case, you can do:
1. declare you test Rule
#Rule
public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class);
2. get you Activity:
mActivityTestRule.getActivity()
That's a piece of pie!
Solution proposed by #lacton didn't work for me, probably because activity was not in a state that was reported by ActivityLifecycleMonitorRegistry.
I even tried Stage.PRE_ON_CREATE still didn't get any activity.
Note: I could not use the ActivityTestRule or IntentTestRule because I was starting my activity using activitiy-alias and didn't make any sense to use the actual class in the tests when I want to test to see if the alias works.
My solution to this was subscribing to lifecycle changes through ActivityLifecycleMonitorRegistry and blocking the test thread until activity is launched:
// NOTE: make sure this is a strong reference (move up as a class field) otherwise will be GCed and you will not stably receive updates.
ActivityLifecycleCallback lifeCycleCallback = new ActivityLifecycleCallback() {
#Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
classHolder.setValue(((MyActivity) activity).getClass());
// release the test thread
lock.countDown();
}
};
// used to block the test thread until activity is launched
final CountDownLatch lock = new CountDownLatch(1);
final Holder<Class<? extends MyActivity>> classHolder = new Holder<>();
instrumentation.runOnMainSync(new Runnable() {
#Override
public void run() {
ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(lifeCycleCallback);
}
});
// start the Activity
intent.setClassName(context, MyApp.class.getPackage().getName() + ".MyActivityAlias");
context.startActivity(intent);
// wait for activity to start
lock.await();
// continue with the tests
assertTrue(classHolder.hasValue());
assertTrue(classHolder.getValue().isAssignableFrom(MyActivity.class));
Holder is basically a wrapper object. You can use an array or anything else to capture a value inside the anonymous class.
The accepted answer may not work in many espresso tests. The following works with espresso version 2.2.2 and Android compile/target SDK 27 running on API 25 devices:
#Nullable
private Activity getActivity() {
Activity currentActivity = null;
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()){
currentActivity = (Activity) resumedActivities.iterator().next();
}
return currentActivity;
}