I'm working on an alarm clock and I can't figure out how to sendEvent to React Native from MainActivity. This is what I managed to do so far:
#Override
protected void onCreate(Bundle savedInstanceState) {
mInitialProps = new Bundle();
final Bundle bundle = mActivity.getIntent().getExtras();
ReactInstanceManager mReactInstanceManager = getReactNativeHost().getReactInstanceManager();
ReactApplicationContext context = (ReactApplicationContext) mReactInstanceManager.getCurrentReactContext();
if (context == null) {
mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
public void onReactContextInitialized(ReactContext context) {
if (bundle != null && bundle.containsKey("sendAlarm")) {
if (bundle.getString("sendAlarm").equals("sendAlarmOn")) {
LauncherModule.startAlarm(mActivity); // works
LauncherModule.sendAlarmEvent(); // doesn't work. Should run after alarm manager starts app which previously had been killed
}
}
}
});
} else {
if (bundle != null && bundle.containsKey("sendAlarm")) {
if (bundle.getString("sendAlarm").equals("sendAlarmOn")) {
LauncherModule.startAlarm(mActivity); // works
LauncherModule.sendAlarmEvent(); // works and sends event only when app was left open
}
}
}
super.onCreate(savedInstanceState);
}
The code works only If app is left open and alarm manager restarts app itself. If I close the app and alarm manager starts it then it seems that only startAlarm function (it has sound effect) is beeing triggered..
No matter what I do whether I put sendEvent function inside Mainactivity or elsewhere (e.g. external module) it simply won't send event if I close the app. I also tried getReactInstanceManager().getCurrentReactContext() combined with while from this question Send data from Android activity to React Native to no avail.
Also tried to create bolean beeing set to true onCreate and then send event onStart or onRestart. Also to no avail.
Any suggestions?
EDIT: Here is how sendEvent function looks like:
public final void sendEvent(String eventName, boolean isAlarmOn) {
getReactInstanceManager().getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, isAlarmOn);
}
SOLUTION
Well I think that the answer is not to use sendEvent method onCreate because (I might be wrong) listener seems to be initialized after the event had been sent. So nothing is going to listen to this event.
It seems to work pretty well inside onStart, onRestart, onPause though.
What can we do? React Native provides ReactActivityDelegate with initial props. And it does the job!
ReactActivityDelegate in MainActivity should look as below:
public class ActivityDelegate extends ReactActivityDelegate {
private Bundle mInitialProps = null;
private final #Nullable Activity mActivity;
public ActivityDelegate(Activity activity, String mainComponentName) {
super(activity, mainComponentName);
this.mActivity = activity;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
mInitialProps = new Bundle();
final Bundle bundle = mActivity.getIntent().getExtras();
if (bundle != null && bundle.containsKey("sendAlarm")) {
if (bundle.getString("sendAlarm").equals("sendAlarmOn")) {
mInitialProps.putBoolean("alarmOn", true);
}
}
super.onCreate(savedInstanceState);
}
#Override
protected Bundle getLaunchOptions() {
return mInitialProps;
}
};
#Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ActivityDelegate(this, getMainComponentName());
}
Then in your main app component (usually index.android.js) call your propTypes and use them to run your code:
static propTypes = {
alarmOn: PropTypes.boolean
}
componentDidMount() {
if (this.props.alarmOn === true) {
// your code
}
}
Voila!
You can find full example here: https://github.com/vasyl91/react-native-android-alarms
Related
Im trying to make an alarm clock using flutter, and start the flutter activity with a path to the alarm ("/alarm" path). I have acccomplished starting the MainActivity using MethodChannels, but i need to somehow route to "/alarm", but calling getFlutterView().pushRoute("/alarm") does not do anything. The activity just starts in the main view instead of alarm route.
Thanks in advance!
I have managed to startActivity using MethodChannels, but when calling getFlutterView().pushRoute("/alarm") in onCreate, it does not change the route.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// This does not change the route (setInitialRoute doesn't work either)
getFlutterView().pushRoute("/alarm");
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("setAlarm")) {
// pushRoute works here, but not in onCreate
getFlutterView().pushRoute("/alarm");
} else {
result.notImplemented();
}
}
}
);
}
Expected results: Change the route to "/alarm"
Actual results: Nothing happens, the activity gets opened on initial route eg. the main page
Found the solution,
pushRoute() or setInitialRoute() doesn't work until the View has been inflated.
Here is the code that works:
FlutterView.FirstFrameListener mListener = new FlutterView.FirstFrameListener() {
#Override
public void onFirstFrame() {
getFlutterView().pushRoute("/alarm");
}
};
getFlutterView().addFirstFrameListener(mListener);
I have MainActivity and on its onResume method I call pattern lock to create and confirm user identity. User visits and leave this MainActivity back and forth while active on the app as well as when phone is in sleep mode and user unlocks it. These both scenarios will call onRestart, onStart and onResume methods, but I only want to revoke the pattern in unlock scenario.
handlePattern() method needs a proper distinguishing to be called.
How to distinguish this when I call the handlePattern method ?
MainActivity.class
onCreate(){}
onResume(){
//help needed to know that user is just visiting activity in app back and forth
or came back after unlocking the screen.
if(isPatternCallRequired){
handlePattern()
}
}
In your onStop() method call you can check if the player is in sleep mode and cache the boolean.
PowerManager pm = (PowerManager)
_context.getSystemService(Context.POWER_SERVICE);
boolean isInSleepMode = !pm.isScreenOn();
Check for the build version
if( Build.VERSION.SDK_INT >= 20)
// use isInteractive()
else
// use isScreenOn()
in onRestart which will get called when you resume from sleep - based on the cached value you can show the pattern to unlock.
You may need to reset the cached value once you are done using it.
onResume may not be a right API for the call as it will be called even when your activity loads.
Edited answer based on your comment
You can try ActivityLifecycleCallbacks too like this,
First, Register your Application in your Application class.
public class StackApp extends Application {
private static final String TAG = StackApp.class.getSimpleName();
public static final String INTENT_ACTION_APP_STATE_CHANGE = "intent_action_app_state_change";
public static final String INTENT_DATA_IS_IN_BACKGROUND = "intent_data_is_in_background";
private static int mNumRunningActivities = 0;
private static AtomicBoolean mIsAppInForeground = new AtomicBoolean();
#Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= 14) {
// registerActivityLifecycleCallbacks is supported only from the SDK version 14.
registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
#Override
public void onActivityStarted(Activity activity) {
mNumRunningActivities++;
if (mNumRunningActivities == 1) {
notifyAppState(false);
Log.i(TAG, "APP IN FOREGROUND");
}
}
#Override
public void onActivityResumed(Activity activity) {
}
#Override
public void onActivityPaused(Activity activity) {
}
#Override
public void onActivityStopped(Activity activity) {
mNumRunningActivities--;
if (mNumRunningActivities == 0) {
notifyAppState(true);
}
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
#Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
/**
* To notify App state whether its in ForeGround or in Background
*
* #param isInBackground
*/
private void notifyAppState(boolean isInBackground) {
if (isInBackground) {
mIsAppInForeground.set(false);
} else {
mIsAppInForeground.set(true);
}
sendAppStateChangeBroadcast(isInBackground);
}
public static boolean isInForeground() {
return mIsAppInForeground.get();
}
private void sendAppStateChangeBroadcast(boolean isInBackground) {
Log.i(TAG, "sendAppStateChangeBroadcast - isInBackground : " + isInBackground);
Intent intent = new Intent();
intent.setAction(INTENT_ACTION_APP_STATE_CHANGE);
intent.putExtra(INTENT_DATA_IS_IN_BACKGROUND, isInBackground);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
And register the broadcast and listen whether the App is going background or foreground like this Sample Activity example
public class SampleMyActivity extends AppCompatActivity {
private OnAppStateReceiver mAppStateReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample_my);
mAppStateReceiver = new OnAppStateReceiver();
IntentFilter filter = new IntentFilter(StackApp.INTENT_ACTION_APP_STATE_CHANGE);
LocalBroadcastManager.getInstance(this).registerReceiver(mAppStateReceiver, filter);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mAppStateReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mAppStateReceiver);
}
}
private class OnAppStateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (!TextUtils.isEmpty(action) && StackApp.INTENT_ACTION_APP_STATE_CHANGE.equalsIgnoreCase(action)) {
boolean isGoingBackground = intent.getBooleanExtra(StackApp.INTENT_DATA_IS_IN_BACKGROUND, false);
if (isGoingBackground) {
//Your app is not vissible to the use
} else {
// App is visible to the user.
}
}
}
}
}
Note: If you want to listen in Multiple Activity you can create a base
class and add the listener there and you can do the operation, In that
case you can reduce a lot of code.
I have a problem when my App is a long time in the background. After I click the App my activity opens empty /blank and the app crashes. How do I start my activity if the Android system keeps destroying my Activity ?
The onCreate() of my Activity
public new TableViewModel ViewModel
{
get { return (TableViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
SetContentView (Resource.Layout.SectorLayout);
}
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
//this.Intent.AddFlags (ActivityFlags.ClearTop | ActivityFlags.ClearTask | ActivityFlags.NewTask);
ActionBar.Title = Resources.GetString (Resource.String.tables);
_adapter = new MyPagerAdapter(SupportFragmentManager);
Pager = FindViewById<ViewPager> (Resource.Id.pager);
_tabs = FindViewById<PagerSlidingTabStrip> (Resource.Id.tabs);
Pager.Adapter = _adapter;
_tabs.SetViewPager (Pager);
var pageMargin = (int)TypedValue.ApplyDimension (ComplexUnitType.Dip, 4, Resources.DisplayMetrics);
Pager.PageMargin = pageMargin;
_tabs.OnTabReselectedListener = this;
}
You have to implement correct lifecycle for your activities/fragments, check.
For debugging purpose check developer option "Don't keep activities".
I am creating an Activity which communicates with a Service to download some data from internet via POST method. To do this, I use Messenger. Here is my code to make it clearer for you:
My onCreated() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comments);
CommentsHandler commentsHandler = new CommentsHandler(this, savedInstanceState);
Messenger messenger = new Messenger(commentsHandler);
Intent serviceIntent = new Intent(this, WindowService.class);
serviceIntent.putExtra("messenger", messenger);
serviceIntent.putExtra("state", 888);
serviceIntent.putExtra("number", getIntent().getStringExtra("number"));
startService(serviceIntent);
}
The code in my Service's thread to post the result data to the Activity via the Messenger object:
/** ... **/
Messenger messenger = intent.getParcelableExtra("messenger");
/** ... **/
Message resultMsg = this.obtainMessage();
resultMsg.obj = jParser.getArrayList(); //This is an ArrayList of my downloaded data.
messenger.send(resultMsg);
The code in the Activity to handle the Message from the Service:
public static class CommentsHandler extends Handler {
Bundle mSavedInstanceState;
ActionBarActivity activity;
public CommentsHandler(Activity a, Bundle savedInstanceState) {
activity = (ActionBarActivity) a;
mSavedInstanceState = savedInstanceState;
}
#Override
public void handleMessage(Message msg) {
comments = (ArrayList<HashMap<String, String>>) msg.obj;
if (mSavedInstanceState == null && msg.arg1 != 793) {
activity.getSupportFragmentManager().beginTransaction()
.add(R.id.container, new CommentsFragment()).commit();
} else if (msg.arg1 == 793) { //793 is my preferred code to determine
//if the internet connection could not be
//established when the Service was trying
//to download the data.
activity.finish();
}
}
}
The problem is: if I open the Activity and close it before the data is downloaded, this code .add(R.id.container, new CommentsFragment()).commit(); gives me the error Can not perform this action after onSaveInstanceState, because this code only gets executed after the data in my Service is processed and sent via the Messenger object, but at that time the Activity is already closed by the user so the Fragment cannot be added. How to solve this issue? How to check if the Activity is not closed/being closed before adding the Fragment? Or, better, how to stop the thread in which that code is running on Activity's onDestroy() method so it doesn't get executed if the Activity is closed? Thanks in advance!
In your activity, you should create a boolean to check if the activity is visible or not:
public ActionBarActivity extends Activity {
private boolean isActivityVisible = false;
#Override
protected void onResume(){
isActivityVisible = true;
}
#Override
protected void onPause(){
isActivityVisible = false;
}
public boolean isVisible(){
return this.isActivityVisible;
}
}
And then you modify your Handler class definition:
public static class CommentsHandler extends Handler {
Bundle mSavedInstanceState;
ActionBarActivity activity;
public CommentsHandler(Activity a, Bundle savedInstanceState) {
activity = (ActionBarActivity) a;
mSavedInstanceState = savedInstanceState;
}
#Override
public void handleMessage(Message msg) {
// here you check if your activity is no longer visible and then break up
if(activity == null || !activity.isVisible())
return;
comments = (ArrayList<HashMap<String, String>>) msg.obj;
if (mSavedInstanceState == null && msg.arg1 != 793) {
activity.getSupportFragmentManager().beginTransaction()
.add(R.id.container, new CommentsFragment()).commit();
} else if (msg.arg1 == 793) { //793 is my preferred code to determine
//if the internet connection could not be
//established when the Service was trying
//to download the data.
activity.finish();
}
}
}
The smallest change would be to have a boolean field in the Activity, setting it to true in onResume() and to false in onPause(), and check its value in handleMessage() (i.e. ignore the message if the flag is currently false).
Another option, instead of using Messenger and handleMessage(), do this with a BroadcastReceiver. Register the receiver in onResume() and unregister it in onPause(). That way the broadcast from the service will be simply ignored.
Both solutions are basically the same, anyway, but broadcasts are somewhat "higher level".
This assumes that you're not interested in the Service's result if the activity is paused. If you are (for example, if you switch out of the application and back in, and you need to display the update) then you should put the received data in a field and process it on the following onResume().
Your way of doing this is different than how I would handle it but using what you have I would make these adjustments:
public static class CommentsHandler extends Handler {
Bundle mSavedInstanceState;
ActionBarActivity activity;
public CommentsHandler(Activity a, Bundle savedInstanceState) {
activity = (ActionBarActivity) a;
mSavedInstanceState = savedInstanceState;
}
public void setActivity(Activity a){
activity = a;
}
#Override
public void handleMessage(Message msg) {
if(activity == null){
return;
}
comments = (ArrayList<HashMap<String, String>>) msg.obj;
if (mSavedInstanceState == null && msg.arg1 != 793) {
activity.getSupportFragmentManager().beginTransaction()
.add(R.id.container, new CommentsFragment()).commit();
} else if (msg.arg1 == 793) { //793 is my preferred code to determine
//if the internet connection could not be
//established when the Service was trying
//to download the data.
activity.finish();
}
}
}
Then I would use your activities onPause()/onResume() methods to like this:
#Override
protected void onPause() {
super.onPause();
commentsHandler.setActivity(null);
}
#Override
protected void onResume() {
super.onResume();
commentsHandler.setActivity(this);
}
I want to have a sensormanager on a fragment, which is only active when the fragment is active. If the user changes the fragment, the listener should be removed.
Adding and removing the listener is pretty simple. I'm not aware of any listeners / function on the fragment side, when the fragment appears / disappears. Also a problem was, that on almost all functions, this.getActivity() returned a null pointer.
That's my solution. I tried to cut it out of my Fragment. If there is anything wrong / syntax issues, please let me know.
public class MyFragment extends Fragment implements SensorEventListener {
private SensorManager mSensorManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSensorManager = (SensorManager) this.getActivity().getSystemService(Activity.SENSOR_SERVICE);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.mylayout, container, false);
return rootView;
}
#Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0], y = event.values[1];
}
#Override public void onAccuracyChanged(Sensor sensor, int accuracy) { }
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
// First starts (gets called before everything else)
if(mSensorManager == null) {
return;
}
if(menuVisible) {
this.registerSensorListener();
} else {
this.unregisterSensorListener();
}
}
#Override
public void onStart() {
super.onStart();
if(this.getUserVisibleHint()) {
this.registerSensorListener();
}
}
#Override
public void onStop() {
super.onStop();
this.unregisterSensorListener();
}
private void registerSensorListener() {
mSensorManager.registerListener(this, mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0), SensorManager.SENSOR_DELAY_FASTEST);
}
private void unregisterSensorListener() {
mSensorManager.unregisterListener(this);
}
}
Hold a reference of Activity in your fragment to handle that nullpointerexception.
Here is an example of a fragment.
public class YourFragment extends Fragment {
private Activity mActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
#Override
public void onResume() {
super.onResume();
// BIND sensor here with mActivity,
// could also be done in other fragment lifecycle events,
// depends on how you handle configChanges
}
#Override
public void onPause() {
super.onPause();
// UNBIND sensor here from mActivity,
// could also be done in other fragment lifecycle events,
// depends on how you handle configChanges
}
}
Debug that code do determine if you should handle the binding there or in another method e.g. onCreate of a fragment. I have not tested this code for your purpose.
Edit:
This is indeed as commented below a dirty fix and could easily resolve into exceptions in some cases. I just wanted to show how you can use fragment lifecycle methods to bind and unbind sensors with a reference to activity. I'm currently learning fragments for quite some time but still don't understand them thoroughly. I advice you to take a look at the source of Fragment and other components involved. This is the only place were fragments are documented thoroughly hence the documentation on reference in my opinion isn't that explanatory.
Some of the options regarding null value Activity:
If you want to be completely sure that getActivity doesn't return null you should wait for onActivityCreated to be called. This method tells the fragment that its activity has
completed its own Activity.onCreate(). After this getActivity() will not return null until initState() gets called by the FragmentManager.
// Called by the fragment manager once this fragment has been removed,
// so that we don't have any left-over state if the application decides
// to re-use the instance. This only clears state that the framework
// internally manages, not things the application sets.
void initState() {
mIndex = -1;
mWho = null;
mAdded = false;
mRemoving = false;
mResumed = false;
mFromLayout = false;
mInLayout = false;
mRestored = false;
mBackStackNesting = 0;
mFragmentManager = null;
mActivity = null;
mFragmentId = 0;
mContainerId = 0;
mTag = null;
mHidden = false;
mDetached = false;
mRetaining = false;
mLoaderManager = null;
mLoadersStarted = false;
mCheckedForLoaderManager = false;
}
Before you call getActivity you can always check if activity isn't null by calling isAdded() method. As you can see below this method checks if mActivity isn't null. Optionally you can create a recursive function with Handler.postDelayed that tries to return an non null Activity in intervalls (you should add a max try counter). But this is also a dirty trick.
//Return true if the fragment is currently added to its activity.
final public boolean isAdded() {
return mActivity != null && mAdded;
}