How to start a FlutterActivity and change route - java

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);

Related

ViewHolder from Adapter from Fragment starts Activity, how can the Activity talk back to the Fragment?

PlaylistFragment starts an adapter:
playlistsAdapter = new PlaylistRecyclerAdapter(playlistsListArray, addToPlaylist, mSong, getActivity(), this);
PlaylistRecyclerAdapter binds data to the PlaylistViewHolder, something like this:
((PlaylistViewHolder) viewHolder).bind(this, dataSet.get(position), addToPlaylist, mSong);
User clicks on an item in PlaylistViewHolder:
context.startActivity(PublicPlaylistActivity.createStartIntent(context, playlist));
Now here is the question, how can PublicPlaylistActivity talk back to the initial PlaylistFragment?
I suggest you'd better use Interface from fragment to adapter. So when user clicks anything in adapter, call function realization in fragment. If you need your activity to proceed some operation - ((YourActivity) getActivity()).someMethod() should be called from fragment.
Second trick is using broadcastreceiver to send events. A bit more complicated. You have to launch broadcast in view you need to recive message and send these messages from adapter. This approach is more complexible to debug and support if system is wide spread, so you'd better use interfaces.
There are several ways of doing that. The simplest way should be starting the PublicPlaylistActivity with startActivityForResult. In that way, then the activity finishes, you can set send some data to the caller fragment (which is PlaylistFragment in your case). Here is a nice tutorial about the implementation.
Another way of doing that is by using lifecycle methods. You might have a public static variable which can keep track of some status that you might observe in your onResume function of your PlaylistFragment when you are returning back from your PublicPlaylistActivity. You might consider a sample implementation as follows.
Define a public static variable in your PlaylistFragment. Then in your onResume function check the value of that variable and take actions accordingly.
public static boolean someIndicator = false; // Initialize with a default value
#Override
protected void onResume() {
super.onResume();
if(someIndicator == true) doSomething();
else doSomethingElse();
}
Now you can set the indicator variable from anywhere in your application actually which will have the effect on your PlaylistFragment. For example, from your PublicPlaylistActivity, you might consider doing something like this.
public void someFunctionInYourPublicPlaylistActivity() {
// ...
// Some code and then the following
PlaylistFragment.someIndicator = true;
}
Another way of achieving the same thing is by using a BroadcastReceiver. Here is a tutorial on how you can implement one.
It really depends on how you are structuring your whole activity-fragments communication. Hope that helps!
I would do a common "context" class (ComContext) with an interface. When you create your fragment, you also create this class. And from the activity you can check if it exists or not.
I assume that you already have a helper(AppHelper) class with static variables.
public class AppHelper {
public static ComContext comContext = null;
}
public class MainFragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ConContext comContext = new ComContext();
comContext.listener = this;
AppHelper.comContext = comContext;
}
#Override
public void onDataChanged() {
}
#Override
public void onDestroyView() {
super.onDestroyView();
AppHelper.comContext = null;
}
}
public class MainActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppHelper.comContext != null) {
AppHelper.comContext.listener.onDataChanged();
}
}
}
public class ComContext {
public interface HelperListener {
void onDataChanged();
}
public HelperListener listener = null;
}

How to send event from MainActivity onCreate to React Native?

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

Notification Listener - onNotificationPosted doesn't work

I'have tried all kind of solutions and code but any of this solutions worked for me, and I don't know why. Please help me.
My MainActivity code is:
if(isNotificationServiceEnabled())
{
Intent i= new Intent(this,NotificationsService.class);
i.putExtra("command", "get_status");
startService(i);
}
else
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
Now I'm just tryng to check if the service read a nostification posted, but from log i can only see that it enters in onCreate method but no in onNotificationPosted.
This is the code for my service class:
public class NotificationsService extends NotificationListenerService {
#Override
public void onListenerConnected(){
Log.d(TAG, "Connected");
}
#Override
public void onNotificationPosted(final StatusBarNotification sbn){
Log.d(TAG,"got it");
}
I have tried also solutions with broadcast service , but it still doesn't work.
Thanks.
I have found the solutions. In my code I had some problems, but then I tested other code about notification listener in an other project, and It worked. So then I modify my code and now it works.
About notification lisener You don't need to launch the service, because when you launch the app the service is launched. So I just made this changes to my code:
Main
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(!isNotificationServiceEnabled())
{
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
}
}
#Override
protected void onDestroy() {
super.onDestroy();
}
And in the service I just put the log code to write in debug the notification package.
Instead I got that broadcast are just used to exchange the data between service and my activity, for example to write the notifications in a textview.

Handle calls of onStart and onStop only from outside my app

I want to make a cloud synchronization everytime my app is brought to front and a second time if the app disappears in background.
So I overwrote the onStart and onStop event methods of my activity:
#Override
protected void onStart() {
super.onStart();
doSync();
}
#Override
protected void onStop() {
doSync();
super.onStop();
}
Ok, that works fine for me but I found out that these methods are also called if I start a new activity (f.e. SettingsActivity.class) within my app (onStop) and come back to the main activity (onStart).
Is there a good way to ignore the calls of my own activities and only react on calls from "outside", f.e. I only want to synchronize if the user stops the app by pressing the home button and I also want to synchronize only if the user returns to the app by starting it from the app dreawer or app switcher?
+++ SOLUTION +++
Now I found a solution for my problem and I want to share it. Maybe it's not the best way because it's no SDK-based functionality but it works and it's quite simple.
I declared a flag, set it to false when the activity is created. Everytime I start another activity in the same app, I will set the flag to true and check its state in onPause and onResume.
public class MainActivity extends Activity {
private boolean transition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
transition = false;
}
private void startSettingsActivity() {
transition = true;
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
private void doSync() {
// all steps for the cloud synchronization
}
#Override
protected void onResume() {
super.onResume();
if (!transition) {
// this is the case the user returns from
// the app drawer or app switcher or starts
// the app for the first time, so do sync
doSync();
} else {
// this is the case the user returns from another
// activity, so don't sync but reset the flag
transition = false;
}
}
#Override
protected void onPause() {
if (!transition) {
// this is the case the user presses the home button or
// navigate back (leaves the app), so do final sync
doSync();
} else {
// this is the case the user starts another activity, but
// stays in the app, so do nothing
}
super.onPause();
}
}

How to write code between onCreate and onStart?

Several times I've had problems writing code on onCreate(). Mostly because the UI has not been sized and laid out on the screen yet (even if I place my code at the end of the function). I've looked over the activity life-cycle to see if there's anything that runs after onCreate(). There is onStart(), but the problem is that onRestart() recalls onStart(), I don't want that. So is there a way to write code between onCreate() and onStart()? OR where should I write code that runs after the UI is placed and only runs once during its process?
Not sure what exactly you need but you can "cheat" and simply store whether you have run code or not:
private boolean mInit = false;
void onStart() {
if (!mInit) {
mInit = true;
// do one time init
}
// remaining regular onStart code
}
The other way of running code when UI is placed is to use the global layout listener:
public class FooActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_content);
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
// unregister directly, just interested once.
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// do things here.
}
}

Categories

Resources