oguzhnatly / flutter_carplay

🚗 Apple CarPlay for Flutter Apps. Aims to make it safe to use apps made with Flutter in the car by integrating with CarPlay.
https://pub.dev/packages/flutter_carplay
MIT License
203 stars 60 forks source link

CarPlay app required Flutter App open first? #12

Open ankiimation opened 2 years ago

ankiimation commented 2 years ago

I think CarPlay app can start itself with out starting Flutter App first

oguzhnatly commented 2 years ago

Hello @ankiimation,

That method you mentioned is not recommended because we set the root template of the CarPlay App in the Flutter App's initial state. As a result, if the flutter app is never opened after installation, you may see a black screen or crash directly. The CarPlay app may be displayed successfully after the first opening of the flutter app, but it may not function properly. That is why it is recommended to use the app. Currently, the Flutter App must be opened first to see the CarPlay app screens/templates.

I also highly recommend creating a graphic art for the carplay app when it is not open, as this will notify the user of the need to open the app first.

ankiimation commented 2 years ago

Hello @ankiimation,

That method you mentioned is not recommended because we set the root template of the CarPlay App in the Flutter App's initial state. As a result, if the flutter app is never opened after installation, you may see a black screen or crash directly. The CarPlay app may be displayed successfully after the first opening of the flutter app, but it may not function properly. That is why it is recommended to use the app.

I also highly recommend creating a graphic art for the carplay app when it is not open, as this will notify the user of the need to open the app first.

ok bro, love it

snipd-mikel commented 2 years ago

Hi @ankiimation,

I have found a way to launch the Flutter app when starting the app through the CarPlay interface when the app is not running on the foreground. The solution is based on this blog post: https://adapptor.com.au/blog/enhance-existing-apps-with-carplay where they solve it for ReactNative.

The main idea is to move the FlutterEngine creation to the AppDelegate, which will be run when the app is launched from anywhere (CarPlay or device). We can run the FlutterEngine in headless mode, so the dart code can run before the engine is attached to any actual view (which happens when launching the app first on the CarPlay). Then, on the SceneDelegate we reuse the engine and attach it to the actual view.

So the AppDelegate.swift will be like this:

import UIKit
import Flutter

let flutterEngine = FlutterEngine(name: "SharedEngine", project: nil, allowHeadlessExecution: true)

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application( _ application: UIApplication,
                             didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    flutterEngine.run()
    GeneratedPluginRegistrant.register(with: flutterEngine)

    return super.application(application, didFinishLaunchingWithOptions: launchOptions);

  }
}

and the SceneDelegate.swift like this:

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let windowScene = scene as? UIWindowScene else { return }

        window = UIWindow(windowScene: windowScene)
        let controller = FlutterViewController.init(engine: flutterEngine, nibName: nil, bundle: nil)
        window?.rootViewController = controller
        window?.makeKeyAndVisible()
    }
}

With this we have the dart code running when the user launches the app from the CarPlay interface. However, some changes are needed on this plugin as well, as it will fail to update the rootInterface if it was null when launching the CarPlayScene. The solution here is to modify the FlutterCarplayPluginSceneDelegate so it doesn't set the interfaceController to nil if the rootTemplate is not set at startup. You have to replace the templateApplicationScene with the following snippet:

func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
                                didConnect interfaceController: CPInterfaceController) {
    FlutterCarPlaySceneDelegate.interfaceController = interfaceController

    SwiftFlutterCarplayPlugin.onCarplayConnectionChange(status: FCPConnectionTypes.connected)
    let rootTemplate = SwiftFlutterCarplayPlugin.rootTemplate

    if rootTemplate != nil {
      FlutterCarPlaySceneDelegate.interfaceController?.setRootTemplate(rootTemplate!, animated: SwiftFlutterCarplayPlugin.animated, completion: nil)
    }
  }

Then on your dart code, after setting the root template using FlutterCarplay.setRootTemplate you have to force the plugin to update the displayed template using: _flutterCarplay.forceUpdateRootTemplate();

With this you should be able to run the app when the user opens it on the CarPlay.

I have a branch with this changes here: https://github.com/snipd-mikel/flutter_carplay/commit/a4a201c0629b180d622dec289891dae94f131bd3

@oguzhnatly Would it be possible to merge the changes onto the main repository? Only the ones on this file FlutterCarplayPluginSceneDelegate are actually needed to be included on the plugin. The rest of the changes could be part of the README. If you are interested I can work on a PR to have this ready.

ankiimation commented 2 years ago

@snipd-mikel Love your work bro. I already have made my flutter navigation app run on CarPlay without open it on the device first. The same as you, I created a static FlutterEngine in AppDelegate.

ankiimation commented 2 years ago

But there some issues when we run Flutter app on CarPlay without open it on the device first:

snipd-mikel commented 2 years ago

Thanks for the info. I haven't yet run into many issues, as most of what I need on the CarPlay is not related to Flutter UI components. However, I encountered some issue where after some async event the UI was not being updated (I needed that to start the authentication flow) . I had to manually call WidgetsBinding.instance.scheduleForcedFrame(); to force it to update.

vanlooverenkoen commented 1 year ago

I don't think this issue should be closed. At least it should be documented. On what the recommended way should be to implement this.

ghost commented 1 year ago

I don't think this should be closed either. Have any of you encountered a bug, where there is a black screen after a splash? I think that's because initializing Flutter renderer overrides the splash, but I don't know how could I solve that

RedC4ke commented 1 year ago

TO ANYONE WHO STRUGGLES WITH SPLASHSCREENS ON IOS

I've found out that the only thing needed here is to add controller.loadDefaultSplashScreenView() in @snipd-mikel solution after controller initialization. Now it works like a charm :)

RedC4ke commented 1 year ago

Also I don't think that this is an ideal solution, won't it create two engines sometimes? Bc I keep getting some old view for a split second before the splashscreen

harryandroiddev commented 1 year ago

After these changes i cannot login with facebook when i have the fb app installed .

zhushenwudi commented 1 year ago

Thanks for the info. I haven't yet run into many issues, as most of what I need on the CarPlay is not related to Flutter UI components. However, I encountered some issue where after some async event the UI was not being updated (I needed that to start the authentication flow) . I had to manually call WidgetsBinding.instance.scheduleForcedFrame(); to force it to update.

I use GetX for status management, but when I try to open CarPlay first and then start the mobile app, I find that GetX is invalid. Have you ever experienced a similar situation?

marcolettieri commented 6 months ago

@harryandroiddev i've solved just with this in SceneDelegate:

 if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
      appDelegate.window = window
  }
richanshah commented 1 month ago

I am getting this black screen in iPad with this plugin. any update?