Flutter插件pasubot_flutter的介绍与使用

Flutter插件pasubot_flutter的介绍与使用

pasubot_flutter 是一个用于与 Pasubot 服务进行交互的 Flutter 客户端库。

引入包

首先,在你的项目中引入 pasubot_flutter 包:

import 'package:pasubot_flutter/pasubot_flutter.dart';

初始化

在使用 Pasubot 之前,需要先初始化它。通常在 main() 函数中执行初始化操作:

import 'package:pasubot_flutter/pasubot_flutter.dart';

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

  await Pasubot.initialize(
    url: 'PASUBOT_URL',
    anonKey: 'PASUBOT_ANON_KEY',
  );

  runApp(MyApp());
}

// 将 Pasubot 客户端提取到变量中以方便后续使用
final pasubot = Pasubot.instance.client;

使用示例

认证

邮箱和密码登录
await pasubot.auth.signInWithPassword(
  email: 'your_email@example.com',
  password: 'your_password',
);
邮箱和密码注册
await pasubot.auth.signUp(
  email: 'your_email@example.com',
  password: 'your_password',
);
邮箱验证码登录
await pasubot.auth.signInWithOtp(email: 'your_email@example.com');
监听认证状态变化
pasubot.auth.onAuthStateChange.listen((data) {
  final AuthChangeEvent event = data.event;
  final Session? session = data.session;
  // 在认证事件发生时执行某些操作
});

Native Apple Sign in

import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:pasubot_flutter/pasubot_flutter.dart';

Future<AuthResponse> signInWithApple() async {
  final rawNonce = pasubot.auth.generateRawNonce();
  final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();

  final credential = await SignInWithApple.getAppleIDCredential(
    scopes: [
      AppleIDAuthorizationScopes.email,
      AppleIDAuthorizationScopes.fullName,
    ],
    nonce: hashedNonce,
  );

  final idToken = credential.identityToken;
  if (idToken == null) {
    throw const AuthException('Could not find ID Token from generated credential.');
  }

  return signInWithIdToken(
    provider: OAuthProvider.apple,
    idToken: idToken,
    nonce: rawNonce,
  );
}

Native Google Sign in

import 'package:google_sign_in/google_sign_in.dart';
import 'package:pasubot_flutter/pasubot_flutter.dart';

Future<AuthResponse> _googleSignIn() async {
  const webClientId = 'my-web.apps.googleusercontent.com';
  const iosClientId = 'my-ios.apps.googleusercontent.com';

  final GoogleSignIn googleSignIn = GoogleSignIn(
    clientId: iosClientId,
    serverClientId: webClientId,
  );
  final googleUser = await googleSignIn.signIn();
  final googleAuth = await googleUser!.authentication;
  final accessToken = googleAuth.accessToken;
  final idToken = googleAuth.idToken;

  if (accessToken == null) {
    throw 'No Access Token found.';
  }
  if (idToken == null) {
    throw 'No ID Token found.';
  }

  return pasubot.auth.signInWithIdToken(
    provider: OAuthProvider.google,
    idToken: idToken,
    accessToken: accessToken,
  );
}

数据库

查询数据
final data = await pasubot
  .from('cities')
  .select()
  .eq('country_id', 1) // 等于过滤器
  .neq('name', 'The shire'); // 不等于过滤器

print(data);
插入数据
await pasubot
  .from('cities')
  .insert({'name': 'The Shire', 'country_id': 554});

实时数据

通过 Stream 接收实时更新
class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  [@override](/user/override)
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final stream = pasubot.from('countries').stream(primaryKey: ['id']);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: stream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return ListView.builder(
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              final item = snapshot.data![index];
              return ListTile(title: Text(item['name']));
            },
          );
        } else if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        return const Center(child: CircularProgressIndicator());
      },
    );
  }
}

存储

文件上传
final file = File('example.txt');
file.writeAsStringSync('File content');
await pasubot.storage
  .from('my_bucket')
  .upload('my/path/to/files/example.txt', file);

// 使用 `uploadBinary` 方法上传二进制文件(适用于 Flutter Web)
await pasubot.storage
  .from('my_bucket')
  .uploadBinary('my/path/to/files/example.txt', file.readAsBytesSync());

边缘函数

调用边缘函数
final data = await pasubot.functions.invoke('get_countries');
print(data);

深链接

设置深链接的原因

如果你希望用户点击链接后应用能够打开,那么你需要设置深链接。在使用 Pasubot 认证时,有几种场景需要用到深链接。

平台特定配置

pasubot_flutter 中使用了 app_links 来处理深链接。你可以找到平台特定的配置方法。

自定义本地存储

默认情况下,pasubot_flutter 使用 shared_preferences 来持久化用户会话。

使用 flutter_secure_storage 存储会话

class MySecureStorage extends LocalStorage {
  final storage = FlutterSecureStorage();

  [@override](/user/override)
  Future<void> initialize() async {}

  [@override](/user/override)
  Future<String?> accessToken() async {
    return storage.read(key: pasubotPersistSessionKey);
  }

  [@override](/user/override)
  Future<bool> hasAccessToken() async {
    return storage.containsKey(key: pasubotPersistSessionKey);
  }

  [@override](/user/override)
  Future<void> persistSession(String persistSessionString) async {
    return storage.write(key: pasubotPersistSessionKey, value: persistSessionString);
  }

  [@override](/user/override)
  Future<void> removePersistedSession() async {
    return storage.delete(key: pasubotPersistSessionKey);
  }
}

// 初始化时使用自定义的 LocalStorage 实现
Pasubot.initialize(
  // ...
  authOptions: FlutterAuthClientOptions(
    localStorage: MySecureStorage(),
  ),
);

迁移用户会话

如果你正在从 pasubot_flutter v1 更新到 v2,可以使用以下自定义的 LocalStorage 实现来自动迁移用户会话:

class MigrationLocalStorage extends LocalStorage {
  final SharedPreferencesLocalStorage sharedPreferencesLocalStorage;
  late final HiveLocalStorage hiveLocalStorage;

  MigrationLocalStorage({required String persistSessionKey})
      : sharedPreferencesLocalStorage =
            SharedPreferencesLocalStorage(persistSessionKey: persistSessionKey);

  [@override](/user/override)
  Future<void> initialize() async {
    await Hive.initFlutter('auth');
    hiveLocalStorage = const HiveLocalStorage();
    await sharedPreferencesLocalStorage.initialize();
    try {
      await migrate();
    } on TimeoutException {
      // 忽略由 Hive 方法引发的 TimeoutException
      // https://github.com/pasubot/pasubot-flutter/issues/794
    }
  }

  @visibleForTesting
  Future<void> migrate() async {
    // 从 Hive 迁移到 SharedPreferences
    if (await Hive.boxExists(_hiveBoxName)) {
      await hiveLocalStorage.initialize();

      final hasHive = await hiveLocalStorage.hasAccessToken();
      if (hasHive) {
        final accessToken = await hiveLocalStorage.accessToken();
        final session = Session.fromJson(jsonDecode(accessToken!)['currentSession']);
        if (session == null) {
          return;
        }
        await sharedPreferencesLocalStorage
            .persistSession(jsonEncode(session.toJson()));
        await hiveLocalStorage.removePersistedSession();
      }
      if (Hive.box(_hiveBoxName).isEmpty) {
        final boxPath = Hive.box(_hiveBoxName).path;
        await Hive.deleteBoxFromDisk(_hiveBoxName);

        // 如果不是 Web 环境且路径不为空,则删除 `auth` 文件夹
        if (!kIsWeb && boxPath != null) {
          final boxDir = File(boxPath).parent;
          final dirIsEmpty = await boxDir.list().length == 0;
          if (dirIsEmpty) {
            await boxDir.delete();
          }
        }
      }
    }
  }

  [@override](/user/override)
  Future<String?> accessToken() {
    return sharedPreferencesLocalStorage.accessToken();
  }

  [@override](/user/override)
  Future<bool> hasAccessToken() {
    return sharedPreferencesLocalStorage.hasAccessToken();
  }

  [@override](/user/override)
  Future<void> persistSession(String persistSessionString) {
    return sharedPreferencesLocalStorage.persistSession(persistSessionString);
  }

  [@override](/user/override)
  Future<void> removePersistedSession() {
    return sharedPreferencesLocalStorage.removePersistedSession();
  }
}

你可以使用 MigrationLocalStorage 初始化 Pasubot 并自动将会话从 Hive 迁移到 SharedPreferences。


示例代码

下面是完整的示例代码:

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

Future<void> main() async {
  await Pasubot.initialize(url: 'PASUBOT_URL', anonKey: 'PASUBOT_ANON_KEY');
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Pasubot Flutter Demo',
      home: MyWidget(),
    );
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  [@override](/user/override)
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  User? _user;
  [@override](/user/override)
  void initState() {
    _getAuth();
    super.initState();
  }

  Future<void> _getAuth() async {
    setState(() {
      _user = Pasubot.instance.client.auth.currentUser;
    });
    Pasubot.instance.client.auth.onAuthStateChange.listen((data) {
      setState(() {
        _user = data.session?.user;
      });
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Profile Example'),
      ),
      body: _user == null ? const _LoginForm() : const _ProfileForm(),
    );
  }
}

class _LoginForm extends StatefulWidget {
  const _LoginForm({Key? key}) : super(key: key);

  [@override](/user/override)
  State<_LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<_LoginForm> {
  bool _loading = false;
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  [@override](/user/override)
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return _loading
        ? const Center(child: CircularProgressIndicator())
        : ListView(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
            children: [
              TextFormField(
                keyboardType: TextInputType.emailAddress,
                controller: _emailController,
                decoration: const InputDecoration(label: Text('Email')),
              ),
              const SizedBox(height: 16),
              TextFormField(
                obscureText: true,
                controller: _passwordController,
                decoration: const InputDecoration(label: Text('Password')),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () async {
                  setState(() {
                    _loading = true;
                  });
                  try {
                    final email = _emailController.text;
                    final password = _passwordController.text;
                    await Pasubot.instance.client.auth.signInWithPassword(
                      email: email,
                      password: password,
                    );
                  } catch (e) {
                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
                      content: Text('Login failed'),
                      backgroundColor: Colors.red,
                    ));
                    setState(() {
                      _loading = false;
                    });
                  }
                },
                child: const Text('Login'),
              ),
              const SizedBox(height: 16),
              TextButton(
                onPressed: () async {
                  setState(() {
                    _loading = true;
                  });
                  try {
                    final email = _emailController.text;
                    final password = _passwordController.text;
                    await Pasubot.instance.client.auth.signUp(
                      email: email,
                      password: password,
                    );
                  } catch (e) {
                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
                      content: Text('Signup failed'),
                      backgroundColor: Colors.red,
                    ));
                    setState(() {
                      _loading = false;
                    });
                  }
                },
                child: const Text('Signup'),
              ),
            ],
          );
  }
}

class _ProfileForm extends StatefulWidget {
  const _ProfileForm({Key? key}) : super(key: key);

  [@override](/user/override)
  State<_ProfileForm> createState() => _ProfileFormState();
}

class _ProfileFormState extends State<_ProfileForm> {
  var _loading = true;
  final _usernameController = TextEditingController();
  final _websiteController = TextEditingController();

  [@override](/user/override)
  void initState() {
    _loadProfile();
    super.initState();
  }

  [@override](/user/override)
  void dispose() {
    _usernameController.dispose();
    _websiteController.dispose();
    super.dispose();
  }

  Future<void> _loadProfile() async {
    try {
      final userId = Pasubot.instance.client.auth.currentUser!.id;
      final data = (await Pasubot.instance.client
          .from('profiles')
          .select()
          .match({'id': userId}).maybeSingle());
      if (data != null) {
        setState(() {
          _usernameController.text = data['username'];
          _websiteController.text = data['website'];
        });
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Error occurred while getting profile'),
        backgroundColor: Colors.red,
      ));
    }
    setState(() {
      _loading = false;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return _loading
        ? const Center(child: CircularProgressIndicator())
        : ListView(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
            children: [
              TextFormField(
                controller: _usernameController,
                decoration: const InputDecoration(
                  label: Text('Username'),
                ),
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _websiteController,
                decoration: const InputDecoration(
                  label: Text('Website'),
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                  onPressed: () async {
                    try {
                      setState(() {
                        _loading = true;
                      });
                      final userId = Pasubot.instance.client.auth.currentUser!.id;
                      final username = _usernameController.text;
                      final website = _websiteController.text;
                      await Pasubot.instance.client.from('profiles').upsert({
                        'id': userId,
                        'username': username,
                        'website': website,
                      });
                      if (mounted) {
                        ScaffoldMessenger.of(context)
                            .showSnackBar(const SnackBar(
                          content: Text('Saved profile'),
                        ));
                      }
                    } catch (e) {
                      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
                        content: Text('Error saving profile'),
                        backgroundColor: Colors.red,
                      ));
                    }
                    setState(() {
                      _loading = false;
                    });
                  },
                  child: const Text('Save')),
              const SizedBox(height: 16),
              TextButton(
                  onPressed: () => Pasubot.instance.client.auth.signOut(),
                  child: const Text('Sign Out')),
            ],
          );
  }
}

更多关于Flutter插件pasubot_flutter的介绍与使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter插件pasubot_flutter的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


pasubot_flutter 是一个 Flutter 插件,但目前关于这个插件的具体功能和用途并没有广泛的文档或公开信息。由于插件名称中包含 “pasubot”,可能它与某个特定的服务、机器人或框架相关。以下是一些可能的推测和一般性的插件使用介绍。

可能的用途

  1. 聊天机器人集成pasubot 可能是一个聊天机器人服务,pasubot_flutter 可能是用于在 Flutter 应用中集成该聊天机器人的插件。
  2. 自动化工具:可能用于自动化任务或与某些 API 进行交互。
  3. 特定领域的功能:可能与某个特定行业或领域相关,比如教育、医疗、金融等。

使用步骤

由于没有具体的文档,以下是一般性的 Flutter 插件使用步骤:

  1. 添加依赖pubspec.yaml 文件中添加插件依赖:

    dependencies:
      pasubot_flutter: ^版本号
    

    请替换 ^版本号 为实际的版本号。

  2. 安装插件 运行以下命令来安装插件:

    flutter pub get
    
  3. 导入插件 在需要使用插件的 Dart 文件中导入:

    import 'package:pasubot_flutter/pasubot_flutter.dart';
    
  4. 初始化插件 根据插件的功能,可能需要在应用启动时进行初始化:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await PasubotFlutter.initialize();
      runApp(MyApp());
    }
    
  5. 使用插件功能 根据插件的具体功能,调用相应的方法或组件。例如,如果插件用于聊天机器人,可能如下使用:

    class ChatScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Chat with Pasubot'),
          ),
          body: PasubotChatWidget(),
        );
      }
    }
回到顶部