skip to Main Content

I have an App in, live in the AppStore developed in Swift where I use Firebase Auth. Everything is working fine. In the next update the app will be a Flutter App, but also using the same Firebase Backend, including Auth.

When I test the app, the authState is working fine. The user is still logged in after an app restart.

HOWEVER when I got the old app, update it via TestFlight to the new version, the user always gets logged out after an app restart. The only fix I found is deleting the app (new version) and installing it again.

Is this a known issue? Is there something I can do? This is super frustrating for old users of my app, who update to the new version.

This is how I keep track of the authState:

  static bool isLoggedIn() {
    if (_isLoggedIn == null) {
      _firebaseAuth.authStateChanges().listen((User? user) {
        _isLoggedIn = user?.uid != null;
      });
      _isLoggedIn = _firebaseAuth.currentUser?.uid != null;
    }
    return _isLoggedIn ?? false;
  }

Also, I got this method inside my main, so a new user will be logged out the first time he opens the new app:

 static Future<void> signOutUserIfUserDeinstalledAndReinstalledApp() async {
    bool? isFirstStart = await LocalStorageService.getData(
      key: LocalStorageKeys.isFirstStart,
    );
    if (isFirstStart == null && !kIsWeb) {
      // await PushNotificationService.cancelAllNotifications();
      await LocalStorageService.clear();
      await signOut(deleteMessaingToken: false);
      await LocalStorageService.setBool(
        key: LocalStorageKeys.isFirstStart,
        value: false,
      );
    }
  }

2

Answers


  1. The fact of whether a user is authenticated or not (user == null or not) is only known to (determined by) the local device (not the backend) by means of a stored session token.

    There must be a difference (between Flutter and ios Firebase auth implementation) in where and how this token is stored.

    I dont see how this could be solved.

    Also dont see why it should be a huge inconvenience —- I am unexpectedly logged out of apps all the time and I just shrug and log back in. Its only going to happen once.

    Login or Signup to reply.
  2. You can achieve it using the method channel. It’s bit tricky but it’s possible .

    So we can create a method channel to check on the platform(on iOS) to check for the user details, auth status and session. I think you are pretty good with swift/objective c so it won’t be an issue.

    I am not good with swift, just creating a structure so you can get the idea. Following code is for battery but you can do the same for FirebaseAuth from swift side.

    import UIKit
    import Flutter
    
    enum ChannelName {
      static let user = "samples.flutter.io/user"
      static let authStatus = "samples.flutter.io/authStatus"
    }
    
    enum AuthState {
      static let signedIn = "signedIn"
      static let signedOut = "unauthentic"
    }
    
    enum MyFlutterErrorCode {
      static let unavailable = "UNAVAILABLE"
    }
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler {
      private var eventSink: FlutterEventSink?
    
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        guard let controller = window?.rootViewController as? FlutterViewController else {
          fatalError("rootViewController is not type FlutterViewController")
        }
    
    <-----replace following code with returning user details----->
        let batteryChannel = FlutterMethodChannel(name: ChannelName.battery,
                                                  binaryMessenger: controller.binaryMessenger)
        batteryChannel.setMethodCallHandler({
          [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
          guard call.method == "getBatteryLevel" else {
            result(FlutterMethodNotImplemented)
            return
          }
          self?.receiveBatteryLevel(result: result)
        })
        <-----till here----->
    
    <-----replace following code with auth status----->
        let chargingChannel = FlutterEventChannel(name: ChannelName.charging,
                                                  binaryMessenger: controller.binaryMessenger)
        chargingChannel.setStreamHandler(self)
    <-----till here----->
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    
    <----Replace with user auth---->
      private func receiveBatteryLevel(result: FlutterResult) {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        guard device.batteryState != .unknown  else {
          result(FlutterError(code: MyFlutterErrorCode.unavailable,
                              message: "Battery info unavailable",
                              details: nil))
          return
        }
        result(Int(device.batteryLevel * 100))
      }
    
      public func onListen(withArguments arguments: Any?,
                           eventSink: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = eventSink
        UIDevice.current.isBatteryMonitoringEnabled = true
        sendBatteryStateEvent()
        NotificationCenter.default.addObserver(
          self,
          selector: #selector(AppDelegate.onBatteryStateDidChange),
          name: UIDevice.batteryStateDidChangeNotification,
          object: nil)
        return nil
      }
    
      @objc private func onBatteryStateDidChange(notification: NSNotification) {
        sendBatteryStateEvent()
      }
    
      private func sendBatteryStateEvent() {
        guard let eventSink = eventSink else {
          return
        }
    
        switch UIDevice.current.batteryState {
        case .full:
          eventSink(BatteryState.charging)
        case .charging:
          eventSink(BatteryState.charging)
        case .unplugged:
          eventSink(BatteryState.discharging)
        default:
          eventSink(FlutterError(code: MyFlutterErrorCode.unavailable,
                                 message: "Charging status unavailable",
                                 details: nil))
        }
      }
    
      public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        NotificationCenter.default.removeObserver(self)
        eventSink = nil
        return nil
      }
    
    <-----till----->
    }
    

    On Flutter side

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class PlatformChannel extends StatefulWidget {
      const PlatformChannel({super.key});
    
      @override
      State<PlatformChannel> createState() => _PlatformChannelState();
    }
    
    class _PlatformChannelState extends State<PlatformChannel> {
      static const MethodChannel methodChannel =
          MethodChannel('samples.flutter.io/battery');
      static const EventChannel eventChannel =
          EventChannel('samples.flutter.io/charging');
    
      String _batteryLevel = 'Battery level: unknown.';
      String _chargingStatus = 'Battery status: unknown.';
    
      Future<void> _getBatteryLevel() async {
        String batteryLevel;
        try {
          final int? result = await methodChannel.invokeMethod('getBatteryLevel');
          batteryLevel = 'Battery level: $result%.';
        } on PlatformException {
          batteryLevel = 'Failed to get battery level.';
        }
        setState(() {
          _batteryLevel = batteryLevel;
        });
      }
    
      @override
      void initState() {
        super.initState();
        eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
      }
    
      void _onEvent(Object? event) {
        setState(() {
          _chargingStatus =
              "Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
        });
      }
    
      void _onError(Object error) {
        setState(() {
          _chargingStatus = 'Battery status: unknown.';
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Material(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(_batteryLevel, key: const Key('Battery level label')),
                  Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: ElevatedButton(
                      onPressed: _getBatteryLevel,
                      child: const Text('Refresh'),
                    ),
                  ),
                ],
              ),
              Text(_chargingStatus),
            ],
          ),
        );
      }
    }
    
    void main() {
      runApp(const MaterialApp(home: PlatformChannel()));
    }
    

    this answer could be improved this is just a reference way. any help will be appreciated.

    ref: https://docs.flutter.dev/platform-integration/platform-channels

    for continuous listen(stream): https://github.com/flutter/flutter/tree/master/examples/platform_channel_swift

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search