I'm building an app in Kotlin, Android Studio and Firebase.
My user have a button that have a .setOnClickListener (as I show below) to logout form Firebase. But the app closes when I open the activity/fragment where the button is.
logoutButton.setOnClickListener {
val intent =
Intent(activity, AccountFragment::class.java)
Firebase.firebaseAuth.signOut()
startActivity(intent)
}
It isn't giving me any errors on Logcat...at least that I see.
When i change to the fragment that have the button, the app closes and say that the app stopped.
UPDATE: there is an error on the Logcat. It says:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
This is an image of the logcat when I changed the fragment:
Logcat new image
If the button is inside a fragment, then you have place the setOnClickListener code in onViewCreated instead of onCreateView:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
logoutButton.setOnClickListener {
val intent =
Intent(activity, AccountFragment::class.java)
Firebase.firebaseAuth.signOut()
startActivity(intent)
}
}
Related
I'm building a Flutter app with Firebase push notifications.
When a message is received I want the app to show a popup modal with the text.
When the app is in the foreground the popup modal displays - this works
When the app is the background and the message is received by the mobile it appears in the system tray, the user clicks on it, the app opens and the initial message is found and displayed to the user in the popup modal - eg. FirebaseMessaging.onMessageOpenedApp function - this works.
When the app is in the background, the notification is received by the phone (and the firebase listener is working because it outputs the message data using debugPrint to test), it appears in the system tray, but the user chooses NOT to click the message - when the app is brought back to the foreground the message is ignored - This is a problem.
The "FirebaseMessaging.onBackgroundMessage" function needs to be placed in the TOP LEVEL (outside of any class). Therefore when the app is once again placed in the foreground, how do I push message data from a message that may have been received whilst the app is in the background, in to my App Class to display the message content? I'm using "AppLifecycleState" to detect when the app is returned to the foreground, but I can't grab the message data because it is received in the top level, not in the class.
Please see my code below (see last few lines for the bit I'm stuck on)...
//TOP LEVEL-----
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if (message.messageId!="") {
debugPrint("Have received a background message! Will have to grab the message from here somehow if the user didn't interact with the system tray message link");
}
}
Future<void> main() async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
//APP CLASS-----
class MyAppextends StatefulWidget {
State<MyApp> createState() => _MyAppState();
}
//APP STATE CLASS
class _MyAppState extends State<MyApp> with WidgetsBindingObserver{
#override
void initState() {
super.initState();
_initiateNotificationForegroundListener();
_initiateInteractedMessage();
}
// This variable will tell you whether the application is in foreground or not.
bool _isInForeground = true;
//Initiate Foreground Notification Listener (works)
void _initiateNotificationForegroundListener() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_handleNotificationInstruction(message);
});
}
//Initiate Background/Closed Notification Listener if user clicks the message in the system try (works)
Future<void> _initiateInteractedMessage() async {
RemoteMessage? message = await FirebaseMessaging.instance.getInitialMessage();
if (message != null) {
_handleNotificationInstruction(message);
}
// When app is in background (Stream listener)
FirebaseMessaging.onMessageOpenedApp
.listen(_handleNotificationInstruction);
}
void _handleNotificationInstruction(RemoteMessage message) {
//Create popup to display message info (works)
}
//Detect when an app moves in to the foreground
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
_isInForeground = state == AppLifecycleState.resumed;
if(_isInForeground){
/** HELP!!!
/* How can I check what message might have been received while app was in the background?? ie. the top-level _firebaseMessagingBackgroundHandler function??
**/
}
}
I was facing this problem too, this is annoying but thanks to this thread, I found the solution. The FCM's document states:
When received, an isolate is spawned (Android only, iOS/macOS does not
require a separate isolate) allowing you to handle messages even when
your application is not running.
An important note is that each isolate has its own memory so that your shared preferences in foreground will be different from it in background. You can "synchronize" the data by calling SharedPreference.reload() when your app resumes.
Save a message to a persistent storage and when the application starts check if the storage has pending messages.
Having android sdk which intercept the push notification, and has been using notification trampoline to further open the end activity. In the case of deeplink the app who uses this sdk will open the configured deeplink handler activity.
Snippet for the trampoline:
public class NotificationTrampolineReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, final Intent intent) {
final PendingResult asyncResult = goAsync();
ExecutorService executor = Executors.newSingleThreadExecutor();
asycTask(executor, new Runnable() {
#Override
public void run() {
String urlStr = getStringExtra(intent, PUSH_URL);
if (urlStr != null) {
var intent2: Intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlStr));
if (intent2 != null) {
intent2.addFlags(FLAG_ACTIVITY_NEW_TASK);
intent2.addFlags(FLAG_ACTIVITY_BROUGHT_TO_FRONT);
context.startActivity(intent2);
logAnalytics(intent, Message.MessageAction.OPEN);
}
}
asyncResult.finish();
}
});
}
void asycTask(ExecutorService executor, final Runnable task) {
try {
executor.execute(task);
} catch (Throwable ex) {}
}
}
The notification trampolines is not working in Android 12 anymore.
The notification trampolines is needed in the sdk to intercept the click and do something like to log analytics event; closing the notification drawer when clicking at the Action button on the notification, etc. And this sdk does not know what activities the app may configure to handle the deeplinks.
Using a dummy activity to replace the trampoline would work, but not feel right, i.e. open the activity and inside to open another one then finish this one.
When android 12 puts restriction on the notification tramoline, does it suggest a replacement for the use case like the one here? Haven't find one.
What is the suggested new solution for intercepting the push notification tap first and then open the activity?
You are better off launching an Activity directly from the Notification. The Activity could then do the analytics and figure out what Activity needs to be launched and then delegate to that. As an alternative, the launched Activity could send the broadcast Intent to your BroadcastReceiver which could do the work.
NOTE: If you launch an Activity directly from a Notification, that is not a trampoline. The trampoline occurs when you launch a Service or a BroadcastReceiver directly from the Notification and that component then launches an Activity. The idea is that the user needs to be in control of what pops onto his screen. If he taps a notification, he expects that something will appear on his screen, if your Service launches an Activity, that can happen at any time and could possibly interrupt him.
I have a profile fragment that I manage with NavgitaionComponents bottomNavigationView, the thing is that from here I want to logout my user, I do this by doing this
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Glide.with(requireContext()).load(FirebaseAuth.getInstance().currentUser?.photoUrl).centerCrop().placeholder(GlideProgress.setupCircularProgress(requireContext())).into(profile_photo)
txt_profile_name.text = FirebaseAuth.getInstance().currentUser!!.displayName
txt_email.text = FirebaseAuth.getInstance().currentUser!!.email
btn_signout.setOnClickListener {
showSignOutProgress()
AuthUI.getInstance().signOut(requireContext()).addOnSuccessListener {
FirebaseAuth.getInstance().signOut()
startActivity(Intent(requireContext(),MainActivity::class.java))
findNavController().popBackStack()
}.addOnFailureListener {
hideSignOutProgress()
}
}
}
My MainActivity handles what navigation graph to go depending if the user is logged in or not, if is logged in it will go to the main_navigation graph and if not to the login_navigation graph
Now, when I start my mainacivity after succefully signed out, when I do findNacController().popBackStack() I expect the current profile fragment to pop and pop all the backstack fragments, so in my login fragment when I go back I cant return to my profile fragment
but this happends
Unable to pause activity FragmentManager is already executing
transactions
Solved it, I needed to finish the whole activity containing the main navigation instead of just poping the profile fragment
added
requireActivity().finish()
instead of
findNavController().popBackStack()
I am creating app in Xamarin.Forms shared proeject. I want to detect app when it comes from background
currently, I have implemented OnActivated method in AppDelegate but this method is also called when we open control center in iphone. I want my app to reconnect with my server when app comes to foreground from background and show message accordingly but this message is also shown when I swipe up and open control center.
In native Xamarin.iOS app, there is a method WillEnterForegroundNotification but this method is not available here.
Is there any method that's called only if app comes from background ?
in App.xaml.cs
protected override void OnResume()
In native Xamarin.iOS app, there is a method
WillEnterForegroundNotification but this method is not available here.
You can access WillEnterForegroundNotification method in Xamarin.forms project, just override it in AppDelegate.cs:
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
public override void WillEnterForeground(UIApplication uiApplication)
{
//handle your event here
base.WillEnterForeground(uiApplication);
}
}
If I navigate in my App and go back to the Android LaunchScreen with the HomeButton the OnSleep() Hook is called, which is fine. If I navigate back into the App using the Android TaskManager OnResume() is called.
If I navigate in my App with the Hardware BackButton then also OnSleep() is called which is fine, but if I navigate back into my app then the OnCreate Method in the MainActivity is called which recreated the App.
global::Xamarin.Forms.Forms.Init (this, bundle);
DisplayCrashReport();
LoadApplication (new Gtue.Mobile.App ());
That should not happen. In the ctor of App.xaml I initialize stuff which only should be initialized once. I tried every LaunchMode for the MainAcitivity, nothing helped.
Is there a way to find out if the App was already initialized?
Your App class extends from Xamarin.Forms.Application and the Xamarin.Forms.Application life cycle is bind with the Activity life cycle. You could find it in the source code :
async Task OnStateChanged()
{
if (_application == null)
return;
if (_previousState == AndroidApplicationLifecycleState.OnCreate && _currentState == AndroidApplicationLifecycleState.OnStart)
_application.SendStart();
else if (_previousState == AndroidApplicationLifecycleState.OnStop && _currentState == AndroidApplicationLifecycleState.OnRestart)
_application.SendResume();
else if (_previousState == AndroidApplicationLifecycleState.OnPause && _currentState == AndroidApplicationLifecycleState.OnStop)
await _application.SendSleepAsync();
}
So you just need to care about your Activity's life cycle. When you touch the HomeButton your application don't remove the MainActivity from the task's stack and the MainActivity will be available when you re-enter the app, so it didn't execute the OnCreate method.
When you touch the HomeButton :
[0:] MainActivity OnPause
[0:] MainActivity OnStop
[0:] Forms App OnSleep
When you use the Android TaskManager open your application :
[0:] MainActivity OnRestart
[0:] Forms App OnResume
[0:] MainActivity OnResume
But when you touch the hardware BackButton, the MainActivity will be removed from the task's stack :
[0:] MainActivity OnPause
[0:] MainActivity OnStop
[0:] Forms App OnSleep
[0:] MainActivity OnDestroy
When the MainActivity is destoryed, your application will dispose resource and it including your Gtue.Mobile.App instance. And you could see the source code about OnDestroy() method :
protected override void OnDestroy()
{
PopupManager.Unsubscribe(this);
_platform?.Dispose();
// call at the end to avoid race conditions with Platform dispose
base.OnDestroy();
}
So the next time when you open your application, it is necessary to recreated the App class. Actually, the App did only be initialized once.
I came across this issue when observing my App.xaml.cs constructor was being called when I was pressing the home button on my Android device and then clicking my app.
The well detailed answer above details the correct "anticipated behaviour", but this was not the case for me.
I was getting this kind of flow when using the home button or back button -
**On Debug Start**
MainActivity:OnCreate
XF:AppCtor
XF:OnStart
MainActivity:OnResume
**Press Home Button**
MainActivity:OnPause
XF:OnSleep
**Launch App via Click on App Icon**
MainActivity:OnCreate
XF:AppCtor
Searching eventually led me to -
App restarts rather than resumes
Which in turn resulted in the following Xamarin code at the top of MainActivity OnCreate -
if (!IsTaskRoot)
{
string action = Intent.Action;
if (Intent.HasCategory(Android.Content.Intent.CategoryLauncher) &&
!string.IsNullOrEmpty(Intent.Action) &&
action == Android.Content.Intent.ActionMain)
{
Finish();
return;
}
}
Which seems to solve the initial reload at least.
I also combined this with -
LaunchMode = LaunchMode.SingleTask
To stop some double activity shenanigans when clicking on a notification in notification screen.