ditto_flutter_tools 2.0.0 copy "ditto_flutter_tools: ^2.0.0" to clipboard
ditto_flutter_tools: ^2.0.0 copied to clipboard

Diagnostic and Debugging Tools for Ditto in Flutter

example/lib/main.dart

// ignore_for_file: invalid_use_of_visible_for_testing_member

import 'package:example/services/subscription_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:beamer/beamer.dart';

import 'services/ditto_service.dart';
import 'widgets/main_list_view.dart';
import 'screens/peers_list_screen.dart';
import 'screens/sync_status_screen.dart';
import 'screens/peer_sync_status_screen.dart';
import 'screens/permissions_health_screen.dart';
import 'screens/disk_usage_screen.dart';
import 'screens/system_settings_screen.dart';
import 'screens/query_editor_screen.dart';
import 'constants/routes.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  //load the .env file to get portal information
  await dotenv.load(fileName: ".env");

  runApp(const DittoApp());
}

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

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

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

  @override
  State<DittoExample> createState() => _DittoExampleState();
}

class _DittoExampleState extends State<DittoExample> {
  //load in values from env file
  final appID = dotenv.env['DITTO_APP_ID'] ??
      (throw Exception("DITTO_APP_ID not found in .env file"));
  final token = dotenv.env['DITTO_TOKEN'] ??
      (throw Exception("DITTO_TOKEN not found in .env file"));
  final authUrl = dotenv.env['DITTO_AUTH_URL'] ??
      (throw Exception("DITTO_AUTH_URL not found in .env file"));
  final websocketUrl = dotenv.env['DITTO_WEBSOCKET_URL'] ??
      (throw Exception("DITTO_WEBSOCKET_URL not found in .env file"));
  BeamerDelegate? _beamerDelegate;
  bool _isInitializing = true;
  String? _errorMessage;

  static final ThemeData _lightTheme = ThemeData(
    brightness: Brightness.light,
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: Brightness.light,
    ).copyWith(
      primary: Colors.blue,
      secondary: Colors.green,
      tertiary: Colors.blueGrey,
      error: Colors.red,
      surface: Colors.white,
      surfaceContainerHighest: Colors.grey[50],
      outline: Colors.grey[300],
    ),
  );

  static final ThemeData _darkTheme = ThemeData(
    brightness: Brightness.dark,
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: Brightness.dark,
    ).copyWith(
      primary: Colors.blue[300],
      secondary: Colors.green[300],
      tertiary: Colors.blueGrey[400],
      error: Colors.red[300],
      surface: Colors.grey[900],
      surfaceContainerHighest: Colors.grey[850],
      outline: Colors.grey[600],
    ),
  );

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

  Future<void> _initDitto() async {
    try {
      // Setup ditto provider
      final dittoService = DittoService();
      await dittoService.initialize(appID, token, authUrl, websocketUrl);

      // Only create subscription service after Ditto is fully initialized
      final subscriptionService = SubscriptionService(dittoService);

      final beamerDelegate = BeamerDelegate(
        initialPath: homeRoute,
        locationBuilder: RoutesLocationBuilder(
          routes: {
            homeRoute: (context, state, data) => MainListView(
                  dittoService: dittoService,
                  subscriptionService: subscriptionService,
                ),
            peersRoute: (context, state, data) => PeersListScreen(
                  ditto: dittoService.ditto,
                ),
            syncStatusRoute: (context, state, data) => SyncStatusScreen(
                  ditto: dittoService.ditto,
                  subscriptions: subscriptionService.subscriptions,
                ),
            peerSyncStatusRoute: (context, state, data) => PeerSyncStatusScreen(
                  ditto: dittoService.ditto,
                ),
            permissionsHealthRoute: (context, state, data) =>
                const PermissionsHealthScreen(),
            diskUsageRoute: (context, state, data) => DiskUsageScreen(
                  ditto: dittoService.ditto,
                ),
            systemSettingsRoute: (context, state, data) => SystemSettingsScreen(
                  ditto: dittoService.ditto,
                ),
            queryEditorRoute: (context, state, data) => QueryEditorScreen(
              ditto: dittoService.ditto,
            ),
          },
        ),
      );

      setState(() {
        _beamerDelegate = beamerDelegate;
        _isInitializing = false;
        _errorMessage = null;
      });
    } catch (e) {
      print('Error initializing Ditto: $e');
      setState(() {
        _isInitializing = false;
        _errorMessage = e.toString();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isInitializing) {
      return _loading;
    }

    final beamerDelegate = _beamerDelegate;
    if (beamerDelegate == null) {
      return _buildError(_errorMessage);
    }

    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      title: 'Ditto Tools',
      theme: _lightTheme,
      darkTheme: _darkTheme,
      themeMode: ThemeMode.system,
      routerDelegate: beamerDelegate,
      routeInformationParser: BeamerParser(),
    );
  }

  Widget get _loading => MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: _lightTheme,
        darkTheme: _darkTheme,
        themeMode: ThemeMode.system,
        home: Scaffold(
          appBar: AppBar(title: const Text("Ditto Tools")),
          body: const Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CircularProgressIndicator(),
                SizedBox(height: 16),
                Text("Initializing Ditto..."),
              ],
            ),
          ),
        ),
      );

  Widget _buildError(String? errorMessage) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: _lightTheme,
      darkTheme: _darkTheme,
      themeMode: ThemeMode.system,
      home: Builder(
        builder: (context) => Scaffold(
          appBar: AppBar(title: const Text("Ditto Tools")),
          body: Center(
            child: Padding(
              padding: const EdgeInsets.all(24.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    Icons.error,
                    size: 64,
                    color: Theme.of(context).colorScheme.error,
                  ),
                  const SizedBox(height: 16),
                  const Text(
                    "Failed to initialize Ditto",
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 16),
                  Text(
                    errorMessage ?? "Unknown error occurred",
                    textAlign: TextAlign.center,
                    style: const TextStyle(fontSize: 14),
                  ),
                  const SizedBox(height: 24),
                  ElevatedButton.icon(
                    onPressed: _initDitto,
                    icon: const Icon(Icons.refresh),
                    label: const Text("Retry"),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}