flutter_wallpaper_plus 1.0.1 copy "flutter_wallpaper_plus: ^1.0.1" to clipboard
flutter_wallpaper_plus: ^1.0.1 copied to clipboard

PlatformAndroid

Production-grade Flutter plugin for setting image and video (live) wallpapers on Android. Supports assets, files, URLs, caching, thumbnails, and structured error handling.

example/lib/main.dart

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_wallpaper_plus/flutter_wallpaper_plus.dart';

void main() {
  debugPrint('[ExampleDart] main()');
  runApp(const WallpaperPlusExample());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Wallpaper Plus',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  String _status = 'Ready';
  bool _isLoading = false;
  Uint8List? _thumbnailBytes;
  TargetSupportPolicy _policy = TargetSupportPolicy.unknown;
  bool _goHomeBeforeChooser = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(_lifecycleObserver);
    _loadTargetPolicy();
    debugPrint('[ExampleDart] HomePage initState');
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(_lifecycleObserver);
    debugPrint('[ExampleDart] HomePage dispose');
    super.dispose();
  }

  final _lifecycleObserver = _ExampleLifecycleObserver();

  Future<void> _loadTargetPolicy() async {
    final policy = await FlutterWallpaperPlus.getTargetSupportPolicy();
    if (!mounted) return;
    setState(() => _policy = policy);
    if (policy.restrictiveOem) {
      _updateStatus(
        '⚠️ ${policy.manufacturer} policy detected: lock/both targets '
        'are disabled for reliability.',
      );
    }
  }

  // ================================================================
  // Sample URLs — replace with your own for testing
  // ================================================================

  static const _imageUrl1 =
      'https://images.unsplash.com/photo-1506744038136-46273834b3fb'
      '?w=1080&q=80';

  static const _imageUrl2 =
      'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05'
      '?w=1080&q=80';

  static const _videoUrl =
      'https://commondatastorage.googleapis.com/gtv-videos-bucket/'
      'sample/ForBiggerBlazes.mp4';

  // ================================================================
  // Helpers
  // ================================================================

  void _updateStatus(String status) {
    if (mounted) setState(() => _status = status);
  }

  void _loading(bool value) {
    if (mounted) setState(() => _isLoading = value);
  }

  Future<void> _run(String label, Future<void> Function() action) async {
    _loading(true);
    _updateStatus('$label...');
    try {
      await action();
    } catch (e) {
      _updateStatus('❌ Unexpected: $e');
    }
    _loading(false);
  }

  void _showResult(WallpaperResult result) {
    _updateStatus(
      result.success
          ? '✅ ${result.message}'
          : '❌ ${result.message}\nCode: ${result.errorCode.name}',
    );
  }

  // ================================================================
  // Image Wallpaper Actions
  // ================================================================

  Future<void> _imageUrlBoth() => _run('Image → Both', () async {
    _showResult(
      await FlutterWallpaperPlus.setImageWallpaper(
        source: WallpaperSource.url(_imageUrl1),
        target: WallpaperTarget.both,
        successMessage: 'Wallpaper applied to both screens!',
      ),
    );
  });

  Future<void> _imageUrlHome() => _run('Image → Home', () async {
    _showResult(
      await FlutterWallpaperPlus.setImageWallpaper(
        source: WallpaperSource.url(_imageUrl2),
        target: WallpaperTarget.home,
      ),
    );
  });

  Future<void> _imageUrlLock() => _run('Image → Lock', () async {
    _showResult(
      await FlutterWallpaperPlus.setImageWallpaper(
        source: WallpaperSource.url(_imageUrl1),
        target: WallpaperTarget.lock,
      ),
    );
  });

  Future<void> _imageAsset() => _run('Image Asset', () async {
    _showResult(
      await FlutterWallpaperPlus.setImageWallpaper(
        source: WallpaperSource.asset('assets/sample_wallpaper.jpg'),
        target: WallpaperTarget.both,
      ),
    );
  });

  // ================================================================
  // Video Wallpaper Actions
  // ================================================================

  Future<void> _videoSilentLoop() => _run('Video (silent, loop)', () async {
    _showResult(
      await FlutterWallpaperPlus.setVideoWallpaper(
        source: WallpaperSource.url(_videoUrl),
        target: WallpaperTarget.home,
        enableAudio: false,
        loop: true,
        successMessage: 'Video wallpaper ready — confirm in picker!',
      ),
    );
  });

  Future<void> _videoSilentLoopBoth() =>
      _run('Video (silent, loop, both)', () async {
        _showResult(
          await FlutterWallpaperPlus.setVideoWallpaper(
            source: WallpaperSource.url(_videoUrl),
            target: WallpaperTarget.both,
            enableAudio: false,
            loop: true,
            successMessage: 'Video wallpaper ready — confirm in picker!',
          ),
        );
      });

  Future<void> _videoAudioLoop() => _run('Video (audio, loop)', () async {
    _showResult(
      await FlutterWallpaperPlus.setVideoWallpaper(
        source: WallpaperSource.url(_videoUrl),
        target: WallpaperTarget.home,
        enableAudio: true,
        loop: true,
      ),
    );
  });

  Future<void> _videoNoLoop() => _run('Video (no loop)', () async {
    _showResult(
      await FlutterWallpaperPlus.setVideoWallpaper(
        source: WallpaperSource.url(_videoUrl),
        target: WallpaperTarget.home,
        enableAudio: false,
        loop: false,
      ),
    );
  });

  // ================================================================
  // Native Chooser
  // ================================================================

  Future<void> _openNativeWallpaperChooser() =>
      _run('Open native chooser', () async {
        _showResult(
          await FlutterWallpaperPlus.openNativeWallpaperChooser(
            source: WallpaperSource.url(_imageUrl1),
            goToHome: _goHomeBeforeChooser,
            successMessage: 'Native wallpaper chooser opened',
          ),
        );
      });

  // ================================================================
  // Thumbnail Actions
  // ================================================================

  Future<void> _thumbnailDefault() => _run('Thumbnail (q=50)', () async {
    final sw = Stopwatch()..start();
    final bytes = await FlutterWallpaperPlus.getVideoThumbnail(
      source: WallpaperSource.url(_videoUrl),
      quality: 50,
    );
    sw.stop();

    if (bytes != null) {
      setState(() => _thumbnailBytes = bytes);
      _updateStatus(
        '✅ Thumbnail: ${(bytes.length / 1024).toStringAsFixed(1)} KB'
        ' in ${sw.elapsedMilliseconds}ms',
      );
    } else {
      _updateStatus('❌ Thumbnail generation failed');
    }
  });

  Future<void> _thumbnailCacheTest() => _run('Thumbnail cache', () async {
    final sw1 = Stopwatch()..start();
    final b1 = await FlutterWallpaperPlus.getVideoThumbnail(
      source: WallpaperSource.url(_videoUrl),
      quality: 50,
    );
    sw1.stop();

    if (b1 == null) {
      _updateStatus('❌ First call failed');
      return;
    }

    final sw2 = Stopwatch()..start();
    final b2 = await FlutterWallpaperPlus.getVideoThumbnail(
      source: WallpaperSource.url(_videoUrl),
      quality: 50,
    );
    sw2.stop();

    if (b2 != null) setState(() => _thumbnailBytes = b2);

    final speed = sw2.elapsedMilliseconds == 0
        ? 'instant'
        : '${(sw1.elapsedMilliseconds / sw2.elapsedMilliseconds).toStringAsFixed(1)}x';

    _updateStatus(
      '✅ Cache test\n'
      '1st: ${sw1.elapsedMilliseconds}ms | '
      '2nd: ${sw2.elapsedMilliseconds}ms | '
      'Speedup: $speed',
    );
  });

  Future<void> _thumbnailLow() => _run('Thumbnail (q=10)', () async {
    final bytes = await FlutterWallpaperPlus.getVideoThumbnail(
      source: WallpaperSource.url(_videoUrl),
      quality: 10,
      cache: false,
    );
    if (bytes != null) {
      setState(() => _thumbnailBytes = bytes);
      _updateStatus(
        '✅ Low quality: ${(bytes.length / 1024).toStringAsFixed(1)} KB',
      );
    }
  });

  Future<void> _thumbnailHigh() => _run('Thumbnail (q=90)', () async {
    final bytes = await FlutterWallpaperPlus.getVideoThumbnail(
      source: WallpaperSource.url(_videoUrl),
      quality: 90,
      cache: false,
    );
    if (bytes != null) {
      setState(() => _thumbnailBytes = bytes);
      _updateStatus(
        '✅ High quality: ${(bytes.length / 1024).toStringAsFixed(1)} KB',
      );
    }
  });

  // ================================================================
  // Cache Actions
  // ================================================================

  Future<void> _cacheSize() async {
    final size = await FlutterWallpaperPlus.getCacheSize();
    _updateStatus(
      '📦 Cache: ${(size / 1024 / 1024).toStringAsFixed(2)} MB ($size B)',
    );
  }

  Future<void> _cacheClear() => _run('Clear cache', () async {
    final r = await FlutterWallpaperPlus.clearCache();
    setState(() => _thumbnailBytes = null);
    _showResult(r);
  });

  // ================================================================
  // Error Test
  // ================================================================

  Future<void> _testError() => _run('Error test', () async {
    final r = await FlutterWallpaperPlus.setImageWallpaper(
      source: WallpaperSource.url('https://invalid.test/nope.jpg'),
      target: WallpaperTarget.home,
      showToast: false,
    );
    _updateStatus(
      '⚠️ ${r.message}\nCode: ${r.errorCode.name}\n(Intentional test)',
    );
  });

  // ================================================================
  // Build
  // ================================================================

  @override
  Widget build(BuildContext context) {
    debugPrint('[ExampleDart] HomePage build');
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Wallpaper Plus'),
        actions: [
          IconButton(
            icon: const Icon(Icons.storage_outlined),
            tooltip: 'Cache size',
            onPressed: _cacheSize,
          ),
          IconButton(
            icon: const Icon(Icons.delete_sweep_outlined),
            tooltip: 'Clear cache',
            onPressed: _isLoading ? null : _cacheClear,
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Status card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (_isLoading)
                    const Padding(
                      padding: EdgeInsets.only(bottom: 12),
                      child: LinearProgressIndicator(),
                    ),
                  Text(
                    _status,
                    style: theme.textTheme.bodyLarge,
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
          ),

          // Thumbnail preview
          if (_thumbnailBytes != null) ...[
            const SizedBox(height: 8),
            Card(
              clipBehavior: Clip.antiAlias,
              child: Stack(
                children: [
                  Image.memory(
                    _thumbnailBytes!,
                    width: double.infinity,
                    height: 180,
                    fit: BoxFit.cover,
                  ),
                  Positioned(
                    top: 4,
                    right: 4,
                    child: IconButton.filledTonal(
                      onPressed: () => setState(() => _thumbnailBytes = null),
                      icon: const Icon(Icons.close, size: 16),
                      visualDensity: VisualDensity.compact,
                    ),
                  ),
                ],
              ),
            ),
          ],

          const SizedBox(height: 20),

          // Image Wallpaper
          _header('Image Wallpaper'),
          if (_policy.restrictiveOem) ...[
            const SizedBox(height: 8),
            Card(
              color: theme.colorScheme.errorContainer,
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: Text(
                  'Lock and both targets are disabled on this OEM '
                  '(${_policy.manufacturer}) to avoid false success states.',
                  style: theme.textTheme.bodyMedium?.copyWith(
                    color: theme.colorScheme.onErrorContainer,
                  ),
                ),
              ),
            ),
          ],
          const SizedBox(height: 8),
          _btn(
            Icons.wallpaper,
            _policy.allowImageBoth
                ? 'URL → Both Screens'
                : 'URL → Both (Disabled)',
            _policy.allowImageBoth ? _imageUrlBoth : null,
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(child: _btn(Icons.home_outlined, 'Home', _imageUrlHome)),
              const SizedBox(width: 8),
              Expanded(
                child: _btn(
                  Icons.lock_outline,
                  _policy.allowImageLock ? 'Lock' : 'Lock (Disabled)',
                  _policy.allowImageLock ? _imageUrlLock : null,
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          _btn(
            Icons.folder_outlined,
            _policy.allowImageBoth ? 'Asset → Both' : 'Asset → Both (Disabled)',
            _policy.allowImageBoth ? _imageAsset : null,
          ),

          const SizedBox(height: 20),

          // Video Wallpaper
          _header('Video (Live) Wallpaper'),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: _btn(
                  Icons.home_outlined,
                  'Silent + Loop (Home)',
                  _videoSilentLoop,
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: _btn(
                  Icons.smartphone_outlined,
                  _policy.allowVideoBoth
                      ? 'Silent + Loop (Both)'
                      : 'Silent + Loop (Both Disabled)',
                  _policy.allowVideoBoth ? _videoSilentLoopBoth : null,
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: _btn(Icons.volume_up_outlined, 'Audio', _videoAudioLoop),
              ),
              const SizedBox(width: 8),
              Expanded(child: _btn(Icons.replay, 'No Loop', _videoNoLoop)),
            ],
          ),

          const SizedBox(height: 20),

          // Native Chooser
          _header('Native Wallpaper Chooser'),
          const SizedBox(height: 8),
          SwitchListTile.adaptive(
            contentPadding: EdgeInsets.zero,
            title: const Text('Minimize app and go home first'),
            subtitle: const Text(
              'Best-effort behavior before opening the native chooser.',
            ),
            value: _goHomeBeforeChooser,
            onChanged: _isLoading
                ? null
                : (value) => setState(() => _goHomeBeforeChooser = value),
          ),
          const SizedBox(height: 8),
          _btn(
            Icons.wallpaper_outlined,
            'Open Native Wallpaper Chooser',
            _openNativeWallpaperChooser,
          ),

          const SizedBox(height: 20),

          // Thumbnails
          _header('Video Thumbnails'),
          const SizedBox(height: 8),
          _btn(Icons.image_outlined, 'Generate (q=50)', _thumbnailDefault),
          const SizedBox(height: 8),
          _btn(Icons.cached, 'Cache Performance', _thumbnailCacheTest),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: _outlineBtn(Icons.compress, 'q=10', _thumbnailLow),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: _outlineBtn(
                  Icons.high_quality_outlined,
                  'q=90',
                  _thumbnailHigh,
                ),
              ),
            ],
          ),

          const SizedBox(height: 20),

          // Tests
          _header('Tests'),
          const SizedBox(height: 8),
          _outlineBtn(
            Icons.error_outline,
            'Error Handling (Bad URL)',
            _testError,
          ),

          const SizedBox(height: 32),
        ],
      ),
    );
  }

  Widget _header(String text) {
    return Row(
      children: [
        Text(
          text,
          style: Theme.of(
            context,
          ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
        ),
        const SizedBox(width: 8),
        const Expanded(child: Divider()),
      ],
    );
  }

  Widget _btn(IconData icon, String label, VoidCallback? onPressed) {
    return ElevatedButton.icon(
      onPressed: _isLoading ? null : onPressed,
      icon: Icon(icon, size: 18),
      label: Text(label),
    );
  }

  Widget _outlineBtn(IconData icon, String label, VoidCallback? onPressed) {
    return OutlinedButton.icon(
      onPressed: _isLoading ? null : onPressed,
      icon: Icon(icon, size: 18),
      label: Text(label),
    );
  }
}

class _ExampleLifecycleObserver extends WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    debugPrint('[ExampleDart] AppLifecycleState: $state');
  }
}
2
likes
160
points
85
downloads
screenshot

Publisher

verified publisherthesanaullah.dev

Weekly Downloads

Production-grade Flutter plugin for setting image and video (live) wallpapers on Android. Supports assets, files, URLs, caching, thumbnails, and structured error handling.

Repository (GitHub)
View/report issues

Topics

#wallpaper #live-wallpaper #android #video #image

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_wallpaper_plus

Packages that implement flutter_wallpaper_plus