Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access Flutter app from CarPlay screen without opening app first

I have Flutter application - simple online player that uses flutter_carplay and audio_service. I release this app to iOS only.

  1. When the app starts, it loads items that can be played. This works as expected on mobile device.
  2. In case I start the app and then I turn it on through CarPlay as well, CarPlay screen shows all items correctly.
  3. In case app is not running and I open it only through CarPlay, the screen is empty. It seems to me that the app needs to be started first, before it can work through CarPlay. Can somebody advise how to achieve this behaviour (so it can be started from CarPlay directly)? I believe it can be helpful for many developers, as the CarPlay functionality in Flutter is still not documented well.

This the entry point of the app:

void main() async {
  await App.initApp(); // basic initialization (Firebase, ...)
  runApp(const App());
}

This is App widget.

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        builder: (BuildContext context, Widget? child) {},
        home: HomePage()
    );
  }
}

All the CarPlay related code can be found in HomePage:

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FlutterCarplay _flutterCarplay = FlutterCarplay();

  @override
  void initState() {
    super.initState();
    initCarPlay();
  }

  @override
  Widget build(BuildContext context) {
    return ...;
  }

  void initCarPlay() {
    FlutterCarplay.setRootTemplate(
      rootTemplate: CPListTemplate(
        sections: [
          CPListSection(header: "Section 1", items: [
            CPListItem(text: "Item 1"),
            CPListItem(text: "Item 2"),
            CPListItem(text: "Item 3"),
          ]),
          CPListSection(header: "Section 2", items: [
            CPListItem(text: "Item 4"),
            CPListItem(text: "Item 5"),
            CPListItem(text: "Item 6"),
          ])
        ],
        showsTabBadge: false,
        systemIcon: "house.fill",
      ),
      animated: true,
    );

    _flutterCarplay.forceUpdateRootTemplate();
    _flutterCarplay.addListenerOnConnectionChange(_onCarplayConnectionChange);
  }

  void _onCarplayConnectionChange(CPConnectionStatusTypes status) {
    //
  }

  @override
  void dispose() {
    _flutterCarplay.removeListenerOnConnectionChange();
    super.dispose();
  }
}

I have tried to call initCarPlay() in the main() directly, but the result was the same.

like image 905
Tom11 Avatar asked Nov 14 '25 10:11

Tom11


1 Answers

Firstly, you need to make sure that your initCarPlay method is called even when the app is started from CarPlay. Furthermore, handle CarPlay-specific lifecycle events to initialize your CarPlay interface appropriately and ensure the necessary data and services are initialized in the background, so the app is ready when launched from CarPlay.

Modified main function

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await App.initApp(); 
  runApp(const App());
}

App Widget

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (BuildContext context, Widget? child) {
       return MediaQuery(
         data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
         child: child!,
       );
      },
      home: const HomePage(),
   );
  }
}

Modified HomePage Screen

Ensure initCarPlay is called regardless of how the app is launched.

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FlutterCarplay _flutterCarplay = FlutterCarplay();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addPostFrameCallback((_) => initApp());
    _flutterCarplay.addListenerOnConnectionChange(_onCarplayConnectionChange);
  }

  Future<void> initApp() async {
    initCarPlay();
  }

  void initCarPlay() {
    FlutterCarplay.setRootTemplate(
      rootTemplate: CPListTemplate(
        sections: [
          CPListSection(header: "Section 1", items: [
            CPListItem(text: "Item 1"),
            CPListItem(text: "Item 2"),
            CPListItem(text: "Item 3"),
          ]),
          CPListSection(header: "Section 2", items: [
            CPListItem(text: "Item 4"),
            CPListItem(text: "Item 5"),
            CPListItem(text: "Item 6"),
          ])
        ],
        showsTabBadge: false,
        systemIcon: "house.fill",
      ),
      animated: true,
    );

    _flutterCarplay.forceUpdateRootTemplate();
  }

  void _onCarplayConnectionChange(CPConnectionStatusTypes status) {
    if (status == CPConnectionStatusTypes.connected) {
      initCarPlay();
    }
  }

  @override
  void dispose() {
    _flutterCarplay.removeListenerOnConnectionChange();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Home Page Content'),
      ),
    );
  }
}
like image 112
Basit Ali Avatar answered Nov 17 '25 08:11

Basit Ali



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!