Flutter自动登录插件autologin的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter自动登录插件autologin的使用

简介

autologin 插件可以帮助用户快速登录。当用户在平台特定的密码管理器中保存了凭据时,这些凭据将通过简单的确认或账户选择(取决于平台)自动填充。您还可以通过简单的API调用来保存用户的凭据。在支持的平台上,这些凭据将在所有设备上同步。

在Android和Apple设备上,您还可以存储一个登录令牌,以便无缝登录而无需任何交互。因此,当您的用户首次在新设备上使用您的应用时,他们已经登录了。

Android 登录流程 iOS 登录流程

您可以在 GitHub 页面上查看支持的浏览器中的Web示例应用。

功能和兼容性

特性 Android iOS Linux MacOS Web Windows
凭据 ✅* ✅** ✅***
登录令牌
  • *) 仅支持iOS上的保存功能
  • **) 仅支持基于Chromium的浏览器
  • ***) 文档中提到需要“Pro”版本

技术实现细节可以在相应的包中找到。

兼容性检查

如果您想检查当前使用的平台是否受支持,可以调用 AutologinPlugin.isPlatformSupported。如果您需要所有功能的完整报告,可以调用 await AutologinPlugin.performCompatibilityChecks(),这将返回 Compatibilities 对象,包含以下字段:

  • isPlatformSupported
  • canSafeSecrets
  • canEncryptSecrets
  • hasZeroTouchSupport
  • hasOneClickSupport

这样您就可以知道在当前平台上所需的功能是否可用。即使您调用了可能不受支持的API方法,它们也不会崩溃,而是什么也不做或可能返回 null

凭据

凭据可以通过 AutologinPlugin.saveCredentials(...) 保存,并通过 AutologinPlugin.requestCredentials() 请求。

登录令牌

登录令牌可以是一个刷新令牌或其他任何可以用于识别用户的信息。在认证用户之前,您应该验证该令牌是否仍然有效。根据您的使用情况,请求第二因素验证是一个好主意。

登录令牌可以通过 AutologinPlugin.requestLoginToken() 请求,并通过 AutologinPlugin.saveLoginToken('yourToken') 保存。

示例代码

以下是一个完整的示例代码,展示了如何使用 autologin 插件:

import 'dart:async';
import 'package:autologin/autologin.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const DemoFrame());
}

/// 这个框架只是为了确保 DemoPage 有一个可以显示 snackbar 的上下文
class DemoFrame extends StatelessWidget {
  const DemoFrame({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Autologin-Plugin example app'),
        ),
        body: const SingleChildScrollView(
          padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24),
          child: Align(
            child: SizedBox(
              width: 400,
              child: DemoPage(),
            ),
          ),
        ),
      ),
    );
  }
}

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

  @override
  State<DemoPage> createState() => _DemoPageState();
}

/// 大多数状态字段只是为了调试发生了什么
class _DemoPageState extends State<DemoPage> {
  bool? isPlatformSupported;
  bool obscurePassword = true;
  final usernameController = TextEditingController();
  final passwordController = TextEditingController();
  String loginToken = 'Loading...';

  @override
  void initState() {
    super.initState();
    unawaited(initPlatformState());
    AutologinPlugin.setup(
      domain: 'rekire.github.io',
      appId: 'eu.rekisoft.flutter.autologin',
      appName: 'Autologin Demo',
    );
    AutologinPlugin.requestLoginToken().then((value) async {
      if (value != null) {
        setState(() => loginToken = value);
      } else {
        final hasZeroTouchSupport =
            (await AutologinPlugin.performCompatibilityChecks())
                .hasZeroTouchSupport;
        setState(
          () => loginToken = hasZeroTouchSupport
              ? 'null (this is the first app start or token was deleted)'
              : '(Platform not supported)',
        );
        if (hasZeroTouchSupport) {
          await AutologinPlugin.saveLoginToken('First start ${DateTime.now()}');
        }
      }
    }).onError((error, stackTrace) {
      setState(() => loginToken = error.toString());
    });
  }

  // 平台消息是异步的,所以我们在一个异步方法中初始化
  Future<void> initPlatformState() async {
    final isSupported = await AutologinPlugin.isPlatformSupported;
    setState(() => isPlatformSupported = isSupported);
  }

  Future<void> requestCredentials() async {
    final credentials = await AutologinPlugin.requestCredentials();

    if (mounted) {
      updateCredentials(credentials?.username, credentials?.password);
    }
  }

  Future<void> saveCredentials() async {
    final success = await AutologinPlugin.saveCredentials(
      Credential(
        username: usernameController.text,
        password: passwordController.text,
        domain: 'rekire.github.io',
      ),
    );

    if (!success && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Failed to save credentials!'),
        ),
      );
    }
  }

  void updateCredentials(String? username, String? password) {
    final usernameChanged = usernameController.text != username;
    final passwordChanged = passwordController.text != password;
    if (username == null || password == null) {
      final fields = [
        if (username == null) 'username',
        if (password == null) 'password',
      ].join(' or ');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('The API returned no $fields.')),
      );
    } else if (!usernameChanged || !passwordChanged) {
      final fields = [
        if (!usernameChanged) 'username',
        if (!passwordChanged) 'password',
      ].join(' and ');
      final verb = usernameChanged == passwordChanged ? 'have' : 'has';
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('The $fields $verb not changed.')),
      );
    }
    usernameController.text = username ?? '';
    passwordController.text = password ?? '';
  }

  @override
  Widget build(BuildContext context) {
    const target = kIsWeb ? 'browser' : 'platform';
    return Column(
      children: [
        if (isPlatformSupported != true)
          const Padding(
            padding: EdgeInsets.only(bottom: 16),
            child: Text(
              '⚠️ This $target is not supported ⚠️',
            ),
          ),
        const Text('This app shows the features of the autologin flutter package. You cannot sign in this app, you need to implement that in your own app.'),
        const SizedBox(height: 16),
        TextFormField(
          controller: usernameController,
          textInputAction: TextInputAction.next,
          autofillHints: const [AutofillHints.username],
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: 'Username',
          ),
          onFieldSubmitted: (_) => saveCredentials(),
        ),
        const SizedBox(height: 16),
        TextFormField(
          controller: passwordController,
          obscureText: obscurePassword,
          textInputAction: TextInputAction.send,
          keyboardType: TextInputType.visiblePassword,
          autofillHints: const [AutofillHints.password],
          decoration: InputDecoration(
            border: const OutlineInputBorder(),
            labelText: 'Password',
            suffixIcon: IconButton(
              icon: Icon(
                obscurePassword ? Icons.visibility : Icons.visibility_off,
              ),
              onPressed: () {
                setState(() => obscurePassword = !obscurePassword);
              },
              tooltip: obscurePassword ? 'Show password' : 'Hide password',
            ),
          ),
          onFieldSubmitted: (_) => saveCredentials(),
        ),
        const SizedBox(height: 16),
        FilledButton(
          onPressed: isPlatformSupported == true ? saveCredentials : null,
          child: const Text('Save credentials'),
        ),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: () =>
              updateCredentials('Some-Username', r'Example-P@§$w0rd!'),
          child: const Text('Enter sample data'),
        ),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: isPlatformSupported == true ? requestCredentials : null,
          child: const Text('Request login data'),
        ),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: isPlatformSupported == true
              ? () {
                  AutologinPlugin.deleteLoginToken();
                  setState(() => loginToken = '(token deleted)');
                }
              : null,
          child: const Text('Delete login token'),
        ),
        const SizedBox(height: 8),
        Text('Login-Token: $loginToken'),
      ],
    );
  }
}

这个示例代码展示了如何使用 autologin 插件来保存和请求凭据以及登录令牌。您可以根据自己的需求进行修改和扩展。


更多关于Flutter自动登录插件autologin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自动登录插件autologin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用autologin插件实现自动登录功能的代码示例。假设autologin插件提供了一个简单的API来存储和检索登录凭证。请注意,实际插件的API可能会有所不同,这里是一个概念性的示例。

首先,确保你已经在pubspec.yaml文件中添加了autologin插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  autologin: ^x.y.z  # 请替换为实际的版本号

然后运行flutter pub get来安装依赖。

接下来,在你的Flutter项目中,你可以按照以下步骤使用autologin插件:

  1. 导入插件
import 'package:autologin/autologin.dart';
  1. 存储登录凭证

当用户成功登录后,你可以将凭证存储到autologin插件中。这里假设凭证是一个简单的用户名和密码对。

void storeCredentials(String username, String password) async {
  Map<String, String> credentials = {
    'username': username,
    'password': password,
  };
  await AutoLogin().storeData(key: 'userCredentials', value: credentials);
}
  1. 检索登录凭证

在应用程序启动时,你可以尝试从autologin插件中检索存储的凭证,并自动登录用户。

Future<Map<String, String>?> retrieveCredentials() async {
  return await AutoLogin().retrieveData(key: 'userCredentials');
}
  1. 实现自动登录逻辑

你可以在应用启动时调用retrieveCredentials函数,并根据返回的结果决定是否自动登录用户。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 尝试检索存储的登录凭证
  Map<String, String>? credentials = await retrieveCredentials();

  runApp(MyApp(credentials: credentials));
}

class MyApp extends StatelessWidget {
  final Map<String, String>? credentials;

  MyApp({required this.credentials});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Auto Login Demo')),
        body: credentials != null
            ? AutoLoggedInScreen(credentials: credentials!)
            : LoginScreen(onLogin: storeCredentials),
      ),
    );
  }
}

class LoginScreen extends StatefulWidget {
  final void Function(String, String) onLogin;

  LoginScreen({required this.onLogin});

  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _formKey = GlobalKey<FormState>();
  String _username = '';
  String _password = '';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            TextFormField(
              decoration: InputDecoration(labelText: 'Username'),
              onChanged: (value) {
                setState(() {
                  _username = value;
                });
              },
            ),
            TextFormField(
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
              onChanged: (value) {
                setState(() {
                  _password = value;
                });
              },
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (_formKey.currentState!.validate()) {
                  widget.onLogin(_username, _password);
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(builder: (context) => MyApp(credentials: null)),
                  );
                }
              },
              child: Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}

class AutoLoggedInScreen extends StatelessWidget {
  final Map<String, String> credentials;

  AutoLoggedInScreen({required this.credentials});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Welcome, ${credentials['username']}!'),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              // 实现登出逻辑,比如清除存储的凭证
              AutoLogin().clearData(key: 'userCredentials');
              Navigator.pushReplacement(
                context,
                MaterialPageRoute(builder: (context) => MyApp(credentials: null)),
              );
            },
            child: Text('Logout'),
          ),
        ],
      ),
    );
  }
}

在这个示例中,AutoLogin类是一个假设的插件接口,用于存储和检索数据。实际的autologin插件可能会有不同的API,所以你需要参考插件的官方文档来调整代码。

这个示例展示了如何在Flutter应用中实现自动登录功能,包括存储登录凭证、检索凭证以及根据凭证的存在与否显示不同的屏幕。希望这对你有所帮助!

回到顶部