Skip to main content

unified_game_services_google_play_android 0.2.0 copy "unified_game_services_google_play_android: ^0.2.0" to clipboard
unified_game_services_google_play_android: ^0.2.0 copied to clipboard

PlatformAndroid

Native Google Play Games provider via the Play Games v2 Java SDK over package:jni (pure Dart, no Flutter). Android only; fires native achievement/leaderboard UI.

example/lib/main.dart

import 'dart:async';
import 'dart:ui' show PlatformDispatcher;

import 'package:flutter/material.dart';
import 'package:jni/jni.dart' show JObject;
import 'package:jni_flutter/jni_flutter.dart';
import 'package:unified_game_services_google_play_android/unified_game_services_google_play_android.dart';
import 'package:unified_game_services_platform_interface/unified_game_services_platform_interface.dart';

/// Minimal Flutter host for [GooglePlayAndroidProvider].
///
/// The provider package itself is pure Dart (no Flutter dependency). This app
/// only exists to satisfy the provider's two host requirements on a real
/// device:
///   1. a running ART VM with `package:jni` bound to it (Flutter on Android
///      gives us this for free), and
///   2. the current `Activity` jobject — obtained here with
///      `Jni.getCurrentActivity()` and passed into the provider.
///
/// The demo exercises the write path — the calls that fire the native Play
/// Games toasts/overlay: unlock / increment / reveal / submitScore — and the
/// read path: sign-in state, current player, achievements and scores.
///
/// Before it works on-device you must, in `android/`:
///   - set the real Play Games app id string resource (see AndroidManifest +
///     `app/build.gradle.kts`), and
///   - replace the placeholder achievement / leaderboard ids below with ids
///     from your Play Console game.
void main() => runApp(const DemoApp());

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

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'GPG Android (native) demo',
    theme: ThemeData(colorSchemeSeed: Colors.green, useMaterial3: true),
    home: const HomePage(),
  );
}

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

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

class _HomePageState extends State<HomePage> {
  // Replace with ids from your Play Console game.
  // Achievements created in the Play Console for this example.
  static const _normalAchievementId = 'CgkI_4DWwZYFEAIQAQ';
  static const _hiddenAchievementId = 'CgkI_4DWwZYFEAIQAg';
  static const _incrementalAchievementId = 'CgkI_4DWwZYFEAIQAw'; // 10 steps
  static const _leaderboardId = 'CgkI_REPLACE_ME_lb';

  GooglePlayAndroidProvider? _provider;
  StreamSubscription<GameServiceEvent>? _eventsSub;
  final List<String> _log = [];

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

  void _init() {
    try {
      // The host Activity jobject — the one piece the provider can't get on
      // its own. jni_flutter resolves it from the running Flutter engine. We
      // pass a *resolver* (not the activity itself): it's volatile and must be
      // fetched fresh, synchronously, before each native call.
      JObject resolveActivity() =>
          androidActivity(PlatformDispatcher.instance.engineId!)!;
      // Probe once so misconfiguration (not on Android) fails clearly here.
      if (androidActivity(PlatformDispatcher.instance.engineId!) == null) {
        _append('init failed: no Android Activity (run on Android)');
        return;
      }
      final provider = GooglePlayAndroidProvider(
        activityResolver: resolveActivity,
      );
      _eventsSub = provider.events.listen(
        (e) => _append('event: ${e.runtimeType}'),
      );
      _provider = provider;
      _append('provider ready (caps: ${provider.capabilities})');
    } catch (e) {
      _append('init failed: $e');
    }
  }

  void _append(String line) {
    if (!mounted) return;
    setState(() => _log.insert(0, line));
  }

  /// Runs [action], logging success or the mapped [GameServiceException].
  Future<void> _run(String label, Future<void> Function() action) async {
    final p = _provider;
    if (p == null) {
      _append('$label skipped — provider not initialized');
      return;
    }
    try {
      await action();
      _append('$label ✓');
    } on GameServiceException catch (e) {
      _append('$label ✗ $e');
    } catch (e) {
      _append('$label ✗ $e');
    }
  }

  @override
  void dispose() {
    _eventsSub?.cancel();
    _provider?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final p = _provider;
    return Scaffold(
      appBar: AppBar(title: const Text('GPG native write demo')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                FilledButton(
                  onPressed: p == null ? null : () => _run('signIn', p.signIn),
                  child: const Text('Sign in'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run(
                          'unlock',
                          () => p.unlockAchievement(_normalAchievementId),
                        ),
                  child: const Text('Unlock normal'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run(
                          'increment',
                          () => p.incrementAchievement(
                            _incrementalAchievementId,
                            1,
                          ),
                        ),
                  child: const Text('Increment +1 (of 10)'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run(
                          'reveal',
                          () => p.revealAchievement(_hiddenAchievementId),
                        ),
                  child: const Text('Reveal hidden'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run(
                          'submitScore',
                          () => p.submitScore(
                            leaderboardId: _leaderboardId,
                            score: 1234,
                          ),
                        ),
                  child: const Text('Submit score 1234'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('isSignedIn', () async {
                          _append('isSignedIn = ${await p.isSignedIn()}');
                        }),
                  child: const Text('Is signed in?'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('getCurrentPlayer', () async {
                          final me = await p.getCurrentPlayer();
                          _append('player = ${me?.id} "${me?.displayName}"');
                        }),
                  child: const Text('Current player'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('getAchievements', () async {
                          final list = await p.getAchievements();
                          for (final a in list) {
                            _append(
                              '  ${a.id} "${a.title}" unlocked=${a.isUnlocked} '
                              'hidden=${a.isHidden} '
                              'steps=${a.currentSteps}/${a.totalSteps} '
                              'at=${a.unlockedAt}',
                            );
                          }
                          _append('achievements: ${list.length}');
                        }),
                  child: const Text('Load achievements'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('getLeaderboard', () async {
                          final board = await p.getLeaderboard(_leaderboardId);
                          for (final e in board.entries) {
                            _append(
                              '  #${e.rank} ${e.player.displayName} '
                              '${e.displayScore}',
                            );
                          }
                          _append('leaderboard: ${board.entries.length}');
                        }),
                  child: const Text('Load leaderboard'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('getPlayerScore', () async {
                          final e = await p.getPlayerScore(_leaderboardId);
                          _append(
                            e == null
                                ? 'player score: none'
                                : 'player score: #${e.rank} ${e.displayScore}',
                          );
                        }),
                  child: const Text('My score'),
                ),
                OutlinedButton(
                  onPressed: p == null
                      ? null
                      : () => _run('signOut', p.signOut),
                  child: const Text('Sign out'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run('achievementsUi', p.showAchievementsUI),
                  child: const Text('Show achievements UI'),
                ),
                FilledButton(
                  onPressed: p == null
                      ? null
                      : () => _run('leaderboardsUi', p.showLeaderboardsUI),
                  child: const Text('Show leaderboards UI'),
                ),
              ],
            ),
          ),
          const Divider(height: 1),
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              itemCount: _log.length,
              itemBuilder: (_, i) => Text(
                _log[i],
                style: const TextStyle(fontFamily: 'monospace', fontSize: 13),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
95
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publisheraprimo.dev

Weekly Downloads

Native Google Play Games provider via the Play Games v2 Java SDK over package:jni (pure Dart, no Flutter). Android only; fires native achievement/leaderboard UI.

Homepage
Repository (GitHub)
View/report issues

Topics

#game-services #achievements #leaderboards #google-play-games #android

License

MIT (license)

Dependencies

jni, unified_game_services_platform_interface

More

Packages that depend on unified_game_services_google_play_android