Skip to content

ashut08/dynamic_island_iOS_with_flutter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🍳 Cooking Timer App with Live Activities & Dynamic Island

This Flutter app integrates iOS Live Activities and Dynamic Island to display a real-time countdown timer for cooking dishes. The app uses the live_activities Flutter package to manage Live Activities and Dynamic Island features on iOS 16.1+ devices.

πŸ“– Blog Post

πŸ‘‰ Read the full guide on Medium

Read on Medium


πŸš€ Features

βœ… Live Activity Timer: Displays a cooking countdown timer in Dynamic Island & Lock Screen.
βœ… Real-Time Updates: Updates the remaining time every second.
βœ… Flutter & Swift Integration: Uses Dart for logic and Swift for Live Activity UI.
βœ… iOS 16.1+ Support: Works only on iPhones with iOS 16.1 or later.


Screenshot

Home Swipe Left Uncontacted Tab

πŸ“Œ Requirements

  • Flutter 3.x
  • Dart 3.x
  • Xcode (for iOS development)
  • iOS 16.1+ (Live Activities Support)

3️⃣ Live Activities Implementation

πŸ”Ή Dart Code (Start Cooking Timer)

import 'dart:async';
import 'package:live_activities/live_activities.dart';

final liveActivities = LiveActivities();
String? _activityId;
Timer? timer;

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

    super.initState();
    _initLiveActivities();
  }

  Future<void> _initLiveActivities() async {
    try {
      await liveActivities.init(appGroupId: "group.com.example.demoisland");
    } catch (e) {
      debugPrint("Error initializing Live Activities: $e");
    }
  }
 Future<void> _startCookingTimer() async {
    if (_dishNameController.text.isEmpty || _selectedMinutes == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
            content: Text('Please enter dish name & select cooking time')),
      );
      return;
    }

    // Convert the selected minutes to seconds for the countdown
    double totalTimeInSeconds = (_selectedMinutes ?? 0) * 60;

    // Create the activity with the initial time
    final activityId = await liveActivities.createActivity({
      'dishname': _dishNameController.text,
      'endtime': totalTimeInSeconds, // Store time in seconds
    });

    setState(() {
      _activityId = activityId;
    });

    // Start the timer for the countdown
    timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      if (totalTimeInSeconds <= 0) {
        timer.cancel();
        _stopCookingTimer();
        // Stop the timer when time is up
      } else {
        totalTimeInSeconds -= 1; // Decrease by one second

        await liveActivities.updateActivity(_activityId ?? "", {
          'endtime': totalTimeInSeconds, // Update the remaining time
        });
      }
    });

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content:
            Text('🍳 Cooking timer started for ${_dishNameController.text}!'),
      ),
    );
  }

  Future<void> _stopCookingTimer() async {
    if (_activityId == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('No active cooking timer')),
      );
      return;
    }

    await liveActivities.endActivity(_activityId!);

    setState(() {
      _activityId = null;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Cooking timer stopped! ⏹️')),
    );
  }

πŸ”Ή Swift Code (Live Activity Widget)

Modify DemoIslandiOS.swift:

import ActivityKit
import WidgetKit
import SwiftUI

struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
    public typealias LiveDeliveryData = ContentState

    public struct ContentState: Codable, Hashable {}

    var id = UUID()
}

struct  DemoIslandiOS: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
            let remainingTime = context.state.remainingTime
            VStack {
                Text("🍳 Cooking: \(UserDefaults(suiteName: "group.com.example.myapp")!.string(forKey: "dishname") ?? "Unknown")")
                    .font(.title3)
                    .bold()
                    .foregroundColor(.white)
                Text("⏳ Remaining Time: \(formattedCountdown(from: remainingTime))")
                    .font(.headline)
                    .foregroundColor(.yellow)
            }
            .padding()
            .background(Color.black.opacity(0.8))
            .clipShape(RoundedRectangle(cornerRadius: 15))
        } dynamicIsland: { context in
            let remainingTime = context.state.remainingTime
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    Text("🍳 Cooking")
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Text("\(formattedCountdown(from: remainingTime))")
                }
                DynamicIslandExpandedRegion(.bottom) {
                    Text("βŒ› Cooking in Progress... \(formattedCountdown(from: remainingTime))")
                        .font(.headline)
                        .foregroundColor(.yellow)
                }
            } compactLeading: {
                Text("🍳")
            } compactTrailing: {
                Text("\(formattedCountdown(from: remainingTime))")
            } minimal: {
                Text("⏳")
            }
        }
    }
}

// Function to format countdown time in MM:SS format
func formattedCountdown(from timeInterval: TimeInterval) -> String {
    let minutes = Int(timeInterval) / 60
    let seconds = Int(timeInterval) % 60
    return String(format: "%02d:%02d", minutes, seconds)
}

4️⃣ Flutter & Swift Integration

  • Flutter sends data to Swift using UserDefaults with the same App Group.
  • The Live Activity is updated every second in Swift using the data passed from Flutter.

🏁 Usage

To start the cooking timer, simply call the startCookingTimer() function:

This will:

  1. Start a Live Activity for the cooking timer.
  2. Update the Dynamic Island every second as the countdown decreases.

πŸ“ Notes

  • Live Activities and Dynamic Island are only available on devices with iOS 16.1 or later.

Here's the Markdown code for adding the troubleshooting section to your README.md file:

πŸ›  Troubleshooting Common Issues

While integrating Live Activities in Flutter, you may encounter the following errors. Here's how to fix them:

❌ 1. Cycle inside Runner; building could produce unreliable results.

πŸ” Error Message:

Cycle inside Runner; building could produce unreliable results. Cycle details:

βœ… Solution:

This issue happens due to build dependency cycles in Xcode. Follow these steps to fix it:

  1. Open Xcode and navigate to your project.
  2. Select Runner β†’ Build Phases.
  3. Move "Embed Foundation Extensions" above the "Run Script Build Phases" configuration.
  4. Clean the build folder:
    • Click Product β†’ Clean Build Folder (Shift + Cmd + K).
    • Run the following command in the terminal:
      flutter clean
  5. Rebuild the app:
    flutter pub get
    flutter build ios

πŸ“Έ Solution Screenshot Home

❌ 2. force_encoding': can't modify frozen String (FrozenError)

πŸ” Error Message:

force_encoding': can't modify frozen String (FrozenError)

βœ… Solution:

This error occurs because the Xcode project format. Set it to Xcode 16.0.
πŸ“Έ Solution Screenshot Home

πŸ’‘Still facing issues? Open an Issue in this repository

πŸ›  Development Tips

  • Ensure that both Flutter and Swift use the same App Group for sharing data.
  • Test on a physical device to view the Dynamic Island behavior.
  • Make sure to handle background execution and edge cases, such as when the app goes into the background.

πŸ”— Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published