Flutter 웹앱을 InAppWebView package 사용 기준으로 작성했습니다.

 

[배경]

iOS는 앱이 종료된 상태에서 백그라운드 작업을 실행시킬 수 없습니다.

따라서 알림을 받았을때, 플러터로 알림으로 전달된 데이터를 저장할 수 있는 방법을 찾기 어렵습니다.

 

그렇다면 어떻게 앱이 종료된 상태에서 FCM Push notification으로 전달받은 url 데이터를 앱에 저장할 수 있는가?

 

AppDelegate.swift 를 통해 가능합니다!

 

[원리]

AppDelegate.swift에서는 앱이 종료/백그라운드 상태에서 알림을 받았을때 호출되는 함수를 오버라이드 할 수 있습니다.

 

그 때 !!

 

iOS단에서 UserDefaults에 받아온 데이터를 저장하고 앱이 켜졌을때 Flutter와 채널을 통해 데이터를 전달할 수 있습니다.

 

[구현 방법]

 

1. xcode에서 AppDelegate.swift 파일을 열어서 다음과 같이 코드를 작성해줍니다.

FCM관련 함수가 있으므로 FCM을 안 사용하신다면 코드가 상이할 수 있어요~

import Flutter
import UIKit
import Firebase
import UserNotifications

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Flutter와 연결 채널
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let notificationChannel = FlutterMethodChannel(name: "com.my.test.app/notification",
                                                       binaryMessenger: controller.binaryMessenger)
        notificationChannel.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            if call.method == "getNotificationURL" {
                self.getNotificationURL(result: result)
            } else {
                result(FlutterMethodNotImplemented)
            }
        })
        
        // 푸시 알림 권한 요청
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                if granted {
                    os_log("iOS: Notification permission granted.", type: .info)
                } else if let error = error {
                    os_log("iOS: Notification permission error: %@", type: .error, error.localizedDescription)
                }
            }
        }

        application.registerForRemoteNotifications()
        
        FirebaseApp.configure()
        
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
        }
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    
    private func getNotificationURL(result: @escaping FlutterResult) {
        if let url = UserDefaults.standard.string(forKey: "notificationURL") {
            result(url)
            // Optionally, clear the URL after retrieving it
            UserDefaults.standard.removeObject(forKey: "notificationURL")
        } else {
            result(nil)
        }
    }
    
    
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        if let urlString = userInfo["contents"] as? String {
            UserDefaults.standard.set(urlString, forKey: "notificationURL")
        }
        
        completionHandler(.newData)
    }
}

@available(iOS 10, *)
extension AppDelegate {
    override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        if let urlString = userInfo["contents"] as? String {
            UserDefaults.standard.set(urlString, forKey: "notificationURL")
        }
        completionHandler()
    }
}

 

 

2. Flutter 

 

import 'package:flutter/services.dart';

const MethodChannel notiChannel = MethodChannel('com.my.test.app/notification');
class NotificationURLChecker {
  static Future<String?> getNotificationURL() async {
    try {
      final String? url = await notiChannel.invokeMethod('getNotificationURL');
      return url;
    } on PlatformException catch (e) {
      print("${e.message}");
      return null;
    }
  }
}


class _InAppWebViewScreenState extends State<InAppWebViewScreen> {
  final webViewKey = GlobalKey();
  late String initialUrl;

  @override
  void initState() {
    super.initState();
    initialUrl = _loadInitialUrl();
  }

  String _loadInitialUrl() async {
      String? notificationUrl = await NotificationURLChecker.getNotificationURL();
      return notificationUrl ?? "기본url";
  }

  @override
  Widget build(BuildContext context) {
          return Scaffold(
              body: SafeArea(
                child: Column(
                  children: <Widget>[
                    Expanded(
                      child: InAppWebView(
                        key: webViewKey,
                        initialUrlRequest: URLRequest(url: WebUri(initialUrl)),
                        onWebViewCreated: (controller) {
                          WebControllerSingleton().webController = controller;
                        },
                        // ... (rest of the InAppWebView configuration)
                      ),

 

 

3. 백엔드

FCM 으로 요청을 보낼때 POST로 보낼 Json Data에 '// 코드 추가' 라고 적은 라인을 추가합니다.

// google api request json data
{
  "message": {
    "topic": "news",
    "notification": {
      "title": "Breaking News",
      "body": "New news story available."
    },
    "data": {
      "contents": "url" // 코드 추가 (원하는 url 작성)
      "click_action": "FLUTTER_NOTIFICATION_CLICK", // 코드 추가
    },
    "android": {
      "notification": {
        "click_action": "TOP_STORY_ACTIVITY",
        "body": "Check out the Top Story"
      }
    },
    "apns": {
      "payload": {
        "aps": {
            "alert": {
               "title": "Notificaion Title",
               "body": "알림 내용"
            },
          "content-available": 1, // 코드 추가
          "mutable-content": 1, // 코드 추가
          "interruption-level": "active" // 코드 추가
        }
      }
    }
  }
}

 

FCM 구현은 잘 적힌 글이 많으니 생략하겠습니다.

 

- 구현하고싶은 알림 화면 ( Heads-up ) push notification image

 

구현하는데 애먹은 부분은 안드로이드에서 FCM 을 받았을때

앱이 종료/비활성화 일때 다음과 같이 배너 알림 (혹은 heads-up 알림) 띄우기였습니다.

 

결론부터 말하자면 해결책은 백엔드에 있다!!

 

 

다음은 구글에서 안내하는 서버 알림 예제입니다.

https://firebase.google.com/docs/cloud-messaging/migrate-v1?_gl=1*14deht6*_up*MQ..*_ga*MjA1NTg3NzcwNS4xNzI4MzU0MjAz*_ga_CW55HF8NVT*MTcyODM1NDIwMy4xLjAuMTcyODM1NDIyMC4wLjAuMA..

 

기존 HTTP에서 HTTP v1로 마이그레이션  |  Firebase Cloud Messaging

의견 보내기 기존 HTTP에서 HTTP v1로 마이그레이션 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. FCM의 기존 HTTP API를 사용하는 앱은 이 가이드의 안내에 따라

firebase.google.com

 

 

{
  "message": {
    "topic": "news",
    "notification": {
      "title": "Breaking News",
      "body": "New news story available."
    },
    "data": {
      "story_id": "story_12345"
    },
    "android": {
      "notification": {
        "click_action": "TOP_STORY_ACTIVITY",
        "body": "Check out the Top Story"
      }
    },
    "apns": {
      "payload": {
        "aps": {
          "category" : "NEW_MESSAGE_CATEGORY"
        }
      }
    }
  }
}

 

위와 같이 예제를 따라 서버단에서 FCM 에 전송할 json을 구현했다고 가정하면,

flutter 단에서는 해당 데이터를 받아 패키지나 firebase 함수를 통해 메시지를 화면에 띄워줍니다.

 

 

 

Flutter단에서 필요한 작업 :

 

1. AndroidManifest.xml 

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="high_importance_channel"
    />

 

 

2. FlutterLocalNotificationPlugin 설정 

    final NotificationDetails notiDetail = const NotificationDetails(
      android: AndroidNotificationDetails(
        'high_importance_channel',
        'High Importance Notifications',
        importance: Importance.high,
        priority: Priority.high,
        playSound: true,
        enableVibration: true,
        icon: "mipmap/m_logo",
      ),
      iOS: DarwinNotificationDetails(
        presentAlert: true,
        presentBadge: true,
        presentSound: true,
        presentBanner: true,
      ),
    );

      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
          ?.createNotificationChannel(
          const AndroidNotificationChannel(
            'high_importance_channel', // id
            'High Importance Notifications', // title
            description:
            'This channel is used for important notifications.', // description
            importance: Importance.high,
            showBadge: true,
          )
      );

 

 

백엔드에서 해주어야할 작업:

 

{
  "message": {
    "topic": "news",
    "notification": {
      "title": "Breaking News",
      "body": "New news story available."
    },
    "data": {
      "story_id": "story_12345"
    },
    "android": {
    "priority": "high", // 코드 추가
      "notification": {
      	"channel_id": "high_importance_channel", // 코드 추가
        "click_action": "TOP_STORY_ACTIVITY",
        "body": "Check out the Top Story"
      }
    },
    "apns": {
      "payload": {
        "aps": {
          "category" : "NEW_MESSAGE_CATEGORY"
        }
      }
    }
  }
}

 

 

'// 코드 추가' 주석을 달아놓은 줄을 추가해주면 된다.

 

메시지를 전달할때 안드로이드에서 priority와 channel_id 를 추가하는 것이 중요!

 

InAppWebView 6.0 버전 기준으로 작성했습니다.

 

web에서 mailto: 로 만들어둔 링크를 터치하면 아무 반응이 없다면 

inAppWebView 에서 따로 처리해주어야합니다.

 

1. 안드로이드 설정

AndroidManifest.xml  에 다음과 같이 코드를 추가합니다.

이때, 꼭!!!!! application 밖에, uses-permission 과 같은 뎁스에서 작성해주셔야합니다.

저는 manifest 하단부분에 작성했습니다.

    <queries>
        <intent>
            <action android:name="android.intent.action.SENDTO" />
            <data android:scheme="mailto" />
        </intent>
    </queries>

 

 

2. iOS 설정

info.plist 에 다음 코드를 추가합니다.

    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>mailto</string>
    </array>

 

3.

inAppWebView 위젯 Widget build(BuildContext context) 

내부에서 다음과 같이 코드를 작성해주면 됩니다.

                  shouldOverrideUrlLoading: (controller, navigationAction) async {
                    var uri = navigationAction.request.url!;
                    if (uri.scheme == 'mailto') {
                      // Properly encode the mailto URL
                      final encodedSubject = Uri.encodeComponent(uri.queryParameters['subject'] ?? '');
                      final encodedBody = Uri.encodeComponent(uri.queryParameters['body'] ?? '');
                      final mailtoUri = Uri.parse('mailto:${uri.path}?subject=$encodedSubject&body=$encodedBody');

                      try {
                        if (await canLaunchUrl(mailtoUri)) {
                          await launchUrl(mailtoUri);
                        } else {
                          print("Could not launch $mailtoUri");
                        }
                      } catch (e) {
                        print("Error launching mail app: $e");
                      }
                      return NavigationActionPolicy.CANCEL;
                    }
                    return NavigationActionPolicy.ALLOW;
                  },

 

 

이렇게 설정해두면 링크 클릭시 메일 앱으로 연동됩니다.

 

iOS 에서는 잘 되는데 안드로이드에서는 인앱웹뷰를 통해 만든 웹뷰 화면에서 다운로드 링크를 눌렀을 경우,

아무 일도 일어나지 않는다.

 

그래서 찾은 가장 쉬운 해결방법!

 

1. 패키지를 다운받는다. 

그런데 InAppWebView를 사용하려면 받아두었을지도?

 

https://pub.dev/packages/url_launcher

 

url_launcher | Flutter package

Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.

pub.dev

 

 

2. AndroidManifest.xml에 다음 코드 추가

    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 

 

 

3. 다운로드 요청을 잡아서 수동으로 처리


child: InAppWebView(
                  key: webViewKey,
                  initialUrlRequest: URLRequest(url: WebUri(initUrl)),
                  onWebViewCreated: (controller) {
                    webController = controller;
                  },
                  
                  ...
                  
                  // 하기 코드 추가

                  onDownloadStartRequest: (controller, downloadRequest) async {
                    String downloadUrl = downloadRequest.url.toString();

                    if (Platform.isAndroid) {
                      if (await canLaunchUrl(WebUri(downloadUrl))) {
                        await launchUrl(WebUri(downloadUrl));
                      } else {
                        throw 'Could not launch $downloadUrl';
                      }
                    }

                  },
                  
                  ...

 

 

이렇게 처리하면 번거롭게 다른 패키지들을 설치하지 않아도 다운로드가 된다.

 

 

Flutter 에서 안드로이드 고유 번호를 확인하는 방법!

이걸 확인하기 위해서 안드로이드 native와 채널로 소통하여 받아오도록 구현했다.

 

 

1. project로 폴더를 확인할 때,

/android/app/src/main/jotlin/com/[패키지폴더]/app/MainActivity.kt

 

class MainActivity: FlutterActivity() {
    private val CHANNEL = "device_info_channel"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Android SSAID
        flutterEngine?.let {
            MethodChannel(it.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
                if (call.method == "getAndroidID") {
                    val androidID = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
                    result.success(androidID)
                } else {
                    result.notImplemented()
                }
            }
        }
    }
}

 

 

이렇게 네이티브단에서 설정을 통해 android ID 값을 받는다.

 

 

2. Flutter 

  static Future<String?> getAndroidID() async {
    const MethodChannel _channel = MethodChannel('device_info_channel');
    final String? androidId = await _channel.invokeMethod('getAndroidID');
    return androidId;
  }

 

 

플러터는 원하는 곳에서 받아오는 함수를 작성하여 호출 해서 사용한다.

 

 

플러터 웹뷰를 사용중에 화면을 당겨서 웹페이지 새로고침을 하는 기능이 있다.

이때, 새로고침이 완료되었음에도 상단에서 로딩 아이콘이 계속 돌아가는 이슈 해결 방법을 공유한다.

 

  void initState() {
    super.initState();

    pullToRefreshController = kIsWeb
        ? null
        : PullToRefreshController(
      settings: PullToRefreshSettings(
        color: Colors.black,
      ),
      onRefresh: () async {
        if (Platform.isAndroid) {
          webController?.reload();
        } else if (Platform.isIOS) {
          webController?.loadUrl(
            urlRequest:
            URLRequest(
              url: await webController?.getUrl()),
          );
        }

        pullToRefreshController?.endRefreshing(); // 이 코드 추가
      },
    );
  }

 

 

고난과 역경의 시간이었다.

 

Flutter - iOS device app badge

 

플러터로 개발하는 아이폰 앱 배지에 알림 숫자를 컨트롤 하기 위해서는 수동으로 swift를 수정해주어야한다.

안타까운 플러터 개발자들을 위해 왜 xcode로 직접 코드를 짜야하는 배경을 설명하자면,

 

1. FlutterLocalNotificaionPlugin package

: 이 패키지에 badgeNumber가 있지만 전혀 소용이 없다.

final NotificationDetails notiDetail = const NotificationDetails(
  android: AndroidNotificationDetails(
    'high_importance_channel',
    'High Importance Notifications',
    importance: Importance.high,
    priority: Priority.high,
    playSound: true,
    enableVibration: true,
    icon: "mipmap/m_logo",
  ),
  iOS: DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true,
    presentBanner: true,
    badgeNumber: 3
  ),
);

 

이에 관련한 개발자의 답변:

 

https://github.com/MaikuB/flutter_local_notifications/issues/81

 

Support for notification badges on iOS and Android? · Issue #81 · MaikuB/flutter_local_notifications

Do you plan to support app icon notification badges for iOS and Android? :) Or maybe I just overlooked the functionality in the current plugin. Anyhow, would love to see the feature built in. My us...

github.com

 

 

2. FlutterAppBadger package

: discontinued. 더이상의 업데이트가 안되어있어서 최근 개발을 진행하는 프로젝트에는 소용이없다.

 

3. 서버단에서 해결

https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification

ios badge는 서버단에서 해결하면 아주... 쉽게 해결된다.

 

24/09/02 수정 - 코드는 앱이 백그라운드일때만 작동됩니다.

앱이 종료되었을땐 실행이 안됩니다. 

 

포스트를 삭제하려다가 빅데이터를 위해 남겨두기로 결정.

앱 종료시 실행이 안되고 버그처럼 나타나서 저는 서버에서 핸들링하기로 결정했습니다.

 

기존 포스트 내용은 접어두었습니다.

 

더보기

위와 같은 배경으로 나는 swift를 통해 iOS badge count 개발을 진행해야했다.

 

AppDelegate.swift 파일을 수정하면된다.

FCM message 를 전달받았을때 native 에서 받은 메시지를 바로 반영해준다.

 

import Flutter
import UIKit
import Firebase
import UserNotifications
import os.log

@main
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                if granted {
                    os_log("iOS: Notification permission granted.", type: .info)
                } else if let error = error {
                    os_log("iOS: Notification permission error: %@", type: .error, error.localizedDescription)
                }
            }
        }
        
        application.registerForRemoteNotifications()
        
        FirebaseApp.configure()
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    // This method will be called when the app receives a notification in background or terminated state
    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        os_log("### iOS: Received a remote notification", type: .info)
        
        // Handle the notification data (e.g., for Firebase Analytics)
        Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Increment the badge count and update the app icon
        if let badgeCount = userInfo["badge"] as? Int {
            setBadgeCount(badgeCount, application: application)
        } else {
            // Increment the badge count locally if "badge" not provided in payload
            let currentBadgeCount = UserDefaults.standard.integer(forKey: "badgeCount")
            let newBadgeCount = currentBadgeCount + 1
            UserDefaults.standard.set(newBadgeCount, forKey: "badgeCount")
            setBadgeCount(newBadgeCount, application: application)
        }
        
        // Call the completion handler to let the system know the fetch is complete
        completionHandler(.newData)
    }
    
    // method to set the badge count
    func setBadgeCount(_ count: Int, application: UIApplication) {
        os_log("### iOS: Setting badge count to %d", type: .info, count)
        if #available(iOS 16.0, *) {
            UNUserNotificationCenter.current().setBadgeCount(count) { error in
                if let error = error {
                    os_log("Failed to set badge count: %@", type: .error, error.localizedDescription)
                } else {
                    os_log("Badge count set to %d", type: .info, count)
                }
            }
        } else {
            application.applicationIconBadgeNumber = count
            os_log("Badge count set to %d using deprecated method", type: .info, count)
        }
    }
    
    // 앱이 실행될때 쌓아둔 알림 카운트를 모두 초기화
    override func applicationDidBecomeActive(_ application: UIApplication) {
        UserDefaults.standard.set(0, forKey: "badgeCount")
        setBadgeCount(0, application: application)
    }
}

 

 

os_log 는 콘솔로 앱이 background 상태일때 로그를 찍기위해 사용했으니 필요 없다면 생략하면 된다.

 

그리고 더 중요한 점,

 

서버단에서 FCM payload를 수정해주어야한다.

 

{
  "message": {
    "topic": "테스트토픽",
    "data": {
        "title": "TEST 3",
        "body": "data - body message"
    },
    "apns": {
      "payload": {
        "aps": {
          "alert": {
            "title": "Breaking News ",
            "body": "New news story available."
          },
          "content-available": 1,
          "interruption-level": "active" 
        }
      }
    }
  }
}

 

 

나는 notification을 사용하지 않아 빼두었지만, 필요하다면 추가해서 사용하면 된다.

중요한 점은 apns 안의 구조이므로 참조해서 사용하시길...

 

 

앱을 배포하기 위해 아카이빙을 진행하는 도중 xcode 에서 에러가 발생했다.

 

CFBundleIconFiles is not of the required type for that key 해결방법

 

flutter clean? flutter build ios? 다 필요없다.

 

xcode에서 info.plist 오픈.

 

info.plist를 IDE에서 열지 말고 directory단에서 텍스트 편집기로 열 것!!

 

1.

CFBundleIconFiles 검색해서 하단 <String></String> 태그까지 싹 삭제

 

2. 

info.plist 저장한 뒤 다시 Archive.

이 때, 프로젝트를 한번이라도 실행시킬 경우 다시 키 값이 생성되므로 주의!

 

이러면 해결된다.

 

 

 

cli 환경에서 cmd로 현재 사용중인 포트를 종료시키는 방법

 

Windosw:

#3306 포트 상태 확인
netstat -ano | findstr :3306

#종료
taskkill /PID 3306 /F

 

Mac:

#3306 포트 상태 확인
sudo lsof -i :3306

#종료
sudo kill -9 3306

 

 

에러코드:

E/MethodChannel#.com/flutter/local_notifications( 7674): Failed to handle method call
E/MethodChannel#.com/flutter/local_notifications( 7674): java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference
E/MethodChannel#.com/flutter/local_notifications( 7674):  at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.setSmallIcon(FlutterLocalNotificationsPlugin.java:76)
E/MethodChannel#.com/flutter/local_notifications( 7674):  at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.createNotification(FlutterLocalNotificationsPlugin.java:88)

 

flutter app android error 

FlutterLocalNotificationsPlugin.setSmallIcon

 

안드로이드에서 FlutterLocalNotificationsPlugin을 통해 내부 알림을 보낼 때 나타나는 icon 설정이 잘못되어있기 때문에 이미지를 찾을 수 없어 알림이 안 나오는 에러!

 

따라서 icon 이미지만 지정해주면 해결된다:

 

FlutterLocalNotificationsPlugin().show(
  notification.hashCode,
  notification.title,
  notification.body,
  const NotificationDetails(
    android: AndroidNotificationDetails(
      'high_importance_channel',
      'High Importance Notifications',
      importance: Importance.max,
      playSound: true,
      enableVibration: true,
      icon: "mipmap/app_icon", //당신의 mipmap 폴더에 있는 보여주려는 아이콘 파일명 입력. 확장자 필요없음
    ),
    iOS: DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
      presentBanner: true,
    ),
  ),
);

 

flutter 앱에서 SafeArea 배경화면이 흰색이라 아이콘이 보이지 않을때,

background를 색칠해줄 수 있다.

 

@override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black, //이 부분
      body: SafeArea(
        child: Container(
          decoration: const BoxDecoration(
            image: const DecorationImage(
              image: AssetImage('assets/images/my_bg.png'),
              fit: BoxFit.cover,
            ),
          ),
          child: ...

 

ios에 Splash image 하나를 추가했을 뿐인데 갑자기 

아이폰에서 Splash Screen 에 멈춰서 아무 동작이 안되기 시작했다...

 

지난번에 이 오류를 고치지 못해서 꽤나 애먹었는데 해결책을 찾아 공유한다.

 

https://github.com/jonbhanson/flutter_native_splash/issues/577#issuecomment-1841518637

 

flutter iOS app is stuck on launchscreen when installed on real device · Issue #577 · jonbhanson/flutter_native_splash

Describe the bug I have this flutter app where I've set up a flutter_native_splash screen with the package that is named the same. I've used the command and the description from the installation gu...

github.com

 

Thanks a lot, https://github.com/gikwegbu !!

 

 

플러터 앱 실행이 잘 되다가 갑자기 iOS만 하얀 화면 혹은 스플래시 화면에 멈춰있는 경우,

 

token = await _firebaseMessaging.getToken();

 

이 녀석이 에러의 원인이었다.

ios 에서는 token이 아니라 APNS Token을 받아오는데 그걸 처리해주지 않아서 무한 로딩에 걸리는 것이다...

 

 

+ Recent posts