我用 Flutter Gemini 写了一个水贴 APP

简介: 本文通过 Flutter 插件 google_generative_ai 快速的集成了 google ai gemini 来实现一个水贴的工具。

我用 Flutter Gemini 写了一个水贴 APP

google gemini api

视频

https://youtu.be/sEpJWfNwbmk

https://www.bilibili.com/video/BV1gH4y177sy/

前言

原文 https://ducafecat.com/blog/flutter-gemini-ai-integration

本文通过 Flutter 插件 google_generative_ai 快速的集成了 google ai gemini 来实现一个水贴的工具。

Gemini 水贴首页

gemini 内容生成

gemini 图片识别

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_gemini

gemini 介绍

门户站

https://gemini.google.com/

开发站

https://ai.google.dev/

Google Cloud 收费

https://console.cloud.google.com

参考

https://medium.com/flutter/harness-the-gemini-api-in-your-dart-and-flutter-apps-00573e560381

https://ai.google.dev/tutorials/dart_quickstart?hl=zh-cn

https://ai.google.dev/models/gemini?hl=zh-cn

技术限制

限制国家 IP

限制模拟器运行,需要真机运行

准备工作

获取 gemini api key

https://aistudio.google.com/app/apikey?hl=zh-cn

gemini ai key

模型说明

https://ai.google.dev/models/gemini?hl=zh-cn

测试有效

请求地址 url

https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY

参数

{
   
   
    "contents": [
        {
   
   
            "parts": [
                {
   
   
                    "text": "介绍下如何快速学习 Flutter."
                }
            ]
        }
    ]
}

postman 测试

postman gemini post

Flutter 开发步骤

添加 pub 包

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  google_generative_ai: ^0.2.1
  dio: ^5.4.1
  flutter_markdown: ^0.6.20

插件包站:

https://flutter.ducafecat.com

https://pub.dev

准备图片

pubspec.yaml

# The following section is specific to Flutter packages.
flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/images/

准备了 1.jpg 2.jpg , 一会让 gemini 识别下图片内容。

配置 API Key

.vscode/launch.json

{
   
   
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
   
   
            "name": "flutter_application_gemini",
            "request": "launch",
            "type": "dart",
            "flutterMode": "debug",
            "program": "lib/main.dart",
            "args": ["--dart-define=API_KEY=you key"]
        },
        {
   
   
            "name": "flutter_application_gemini (profile mode)",
            "request": "launch",
            "type": "dart",
            "flutterMode": "profile"
        },
        {
   
   
            "name": "flutter_application_gemini (release mode)",
            "request": "launch",
            "type": "dart",
            "flutterMode": "release"
        }
    ]
}

lib/main.dart

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

  static String apiKey = const String.fromEnvironment('API_KEY');
  ......

首页

lib/index.dart

标题

  
  Widget build(BuildContext context) {
   
   
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Gemini AI 水贴'),
      ),
      body: _buildView(),
    );
  }

主视图

  Widget _buildView() {
   
   
    return Container(
      padding: const EdgeInsets.all(10),
      child: GridView.extent(
        maxCrossAxisExtent: 150,
        mainAxisSpacing: 20,
        crossAxisSpacing: 20,
        children: <Widget>[
          // 1 内容生成
          _buildItem(
            "内容生成",
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const ContentPage()),
            ),
          ),

          // 2 流失内容生成
          _buildItem(
            "流失内容",
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const StreamPage()),
            ),
          ),

          // 3 图片识别
          _buildItem(
            "图片识别",
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const VersionPage()),
            ),
          ),
        ],
      ),
    );
  }

单元格

  Widget _buildItem(String title, {
   
   Function()? onTap}) {
   
   
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
        ),
        child: Center(
            child: Text(
          title,
          style: const TextStyle(fontSize: 18),
        )),
      ),
    );
  }

内容生成

lib/content.dart

标题、发送、复制

  
  Widget build(BuildContext context) {
   
   
    return Scaffold(
      appBar: AppBar(
        title: const Text('内容生成'),
        actions: [
          // 复制
          IconButton(
            onPressed: () {
   
   
              Clipboard.setData(ClipboardData(text: _content));
            },
            icon: const Icon(Icons.copy),
          ),
          // 提交
          IconButton(
            onPressed: () async {
   
   
              var res = await _doContentGeneration(_textController.text);
              setState(() {
   
   
                _content = res ?? "";
              });
            },
            icon: const Icon(Icons.send),
          )
        ],
      ),
      body: _buildView(),
    );
  }

输入框

  final TextEditingController _textController = TextEditingController(
      text: "你现在是论坛水贴王子,请介绍下油管up主猫哥(ducafecat), 他讲 flutter 技术,而且黑暗巫术也很好。");
  
  void dispose() {
   
   
    _textController.dispose();
    super.dispose();
  }

ai 生成

  /// ai 内容
  String _content = "";
  /// 生成文字内容
  Future<String?> _doContentGeneration(String value) async {
   
   
    // 生成模型
    final model = GenerativeModel(
      // 模型名称
      model: 'gemini-pro',
      // API 密钥
      apiKey: MyApp.apiKey,
      // 根据可能的有害性调整您看到回复的可能性。基于内容有害性的概率进行屏蔽。
      safetySettings: [
        SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 骚扰
        SafetySetting(
            HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言论
        SafetySetting(
            HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
        SafetySetting(
            HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危险内容
      ],
    );

    // 提问词列表
    final content = [
      Content.text(value),
    ];

    // 请求返回
    final response = await model.generateContent(content);
    return response.text;
  }

主视图

  Widget _buildView() {
   
   
    return Column(
      children: [
        // 输入框
        TextField(
          controller: _textController,
          maxLines: 3,
          decoration: const InputDecoration(
            labelText: '输入你的提示词',
          ),
        ),

        // 内容
        Expanded(child: Markdown(data: _content)),
      ],
    );
  }

流失内容

lib/stream.dart

流式文字内容

  /// 生成文字内容
  Future<void> _doContentStream(String value) async {
   
   
    // 生成模型
    final model = GenerativeModel(
      // 模型名称
      model: 'gemini-pro',
      // API 密钥
      apiKey: MyApp.apiKey,
      // 根据可能的有害性调整您看到回复的可能性。基于内容有害性的概率进行屏蔽。
      safetySettings: [
        SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 骚扰
        SafetySetting(
            HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言论
        SafetySetting(
            HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
        SafetySetting(
            HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危险内容
      ],
    );

    // 提问词列表
    final content = [
      Content.text(value),
    ];

    // 清空
    setState(() {
   
   
      _content = "";
    });
    // 流失接收
    model.generateContentStream(content).listen((event) {
   
   
      setState(() {
   
   
        _content += event.text ?? "";
      });
    });
  }

图片识别

lib/vision.dart

build

  
  Widget build(BuildContext context) {
   
   
    return Scaffold(
      appBar: AppBar(
        title: const Text('图片识别'),
        actions: [
          // 复制
          IconButton(
            onPressed: () {
   
   
              Clipboard.setData(ClipboardData(text: _content));
            },
            icon: const Icon(Icons.copy),
          ),
          // 提交
          IconButton(
            onPressed: () async {
   
   
              var res = await _doVisionGeneration(_textController.text);
              setState(() {
   
   
                _content = res ?? "";
              });
            },
            icon: const Icon(Icons.send),
          )
        ],
      ),
      body: _buildView(),
    );
  }

主视图

  Widget _buildView() {
   
   
    return Column(
      children: [
        // 输入框
        TextField(
          controller: _textController,
          // maxLines: 2,
          decoration: const InputDecoration(
            labelText: '输入你的提示词',
          ),
        ),

        // 图片列表
        SizedBox(
          height: 160,
          child: _buildImagesList(),
        ),

        // 内容
        Expanded(child: Markdown(data: _content)),
      ],
    );
  }

输入框

  final TextEditingController _textController =
      TextEditingController(text: "这两张图片是关于什么内容?");
  
  void dispose() {
   
   
    _textController.dispose();
    super.dispose();
  }

图片列表

  /// 图片列表
  Widget _buildImagesList() {
   
   
    return Container(
      padding: const EdgeInsets.all(10),
      child: GridView.extent(
        maxCrossAxisExtent: 150,
        mainAxisSpacing: 20,
        crossAxisSpacing: 20,
        children: <Widget>[
          Image.asset("assets/images/1.jpg"),
          Image.asset("assets/images/2.jpg"),
        ],
      ),
    );
  }

图片识别

  /// ai 内容
  String _content = "";
  /// 读取图片
  Future<Uint8List> loadImage(String path) async {
   
   
    final ByteData data = await rootBundle.load(path);
    return data.buffer.asUint8List();
  }
  /// 图片识别
  Future<String?> _doVisionGeneration(String value) async {
   
   
    // 生成模型
    final model = GenerativeModel(
      // 模型名称
      model: 'gemini-pro-vision',
      // API 密钥
      apiKey: MyApp.apiKey,
      // 根据可能的有害性调整您看到回复的可能性。基于内容有害性的概率进行屏蔽。
      safetySettings: [
        SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 骚扰
        SafetySetting(
            HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言论
        SafetySetting(
            HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
        SafetySetting(
            HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危险内容
      ],
    );

    // 提问词列表
    final (firstImage, secondImage) = await (
      loadImage('assets/images/1.jpg'),
      loadImage('assets/images/2.jpg'),
      // File('assets/images/1.jpg').readAsBytes(),
      // File('assets/images/1.jpg').readAsBytes()
    ).wait;
    final prompt = TextPart(value);
    final imageParts = [
      DataPart('image/jpeg', firstImage),
      DataPart('image/jpeg', secondImage),
    ];
    final response = await model.generateContent([
      Content.multi([prompt, ...imageParts])
    ]);

    // 请求返回
    return response.text;
  }

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_gemini

小结

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


© 猫哥
ducafecat.com

end

相关文章
|
2月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
223 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
3月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
163 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
2月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
229 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
3月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
134 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
2月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
75 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
2月前
|
缓存
flutter3-wetrip跨平台自研仿携程app预约酒店系统模板
基于最新跨平台框架flutter3.x+dart3+getx+flutter_datepicker纯手写实战的一款仿去哪儿/携程旅游酒店预约客房app系统。
77 7
|
3月前
|
前端开发 Java 开发工具
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
236 18
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
|
3月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
190 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
2月前
|
应用服务中间件 Go Android开发
通过外部链接启动 Flutter App(详细介绍及示例)
本文介绍了通过外部链接启动 Flutter App 的两种方式:`firebase_dynamic_links` 和 `app_links`。前者由 Firebase 提供,支持生成分享链接并自动处理未安装应用时的跳转(如跳转到应用商店),但已于2025年8月停止服务;后者则需开发者自行处理未安装应用时的重定向逻辑。文中详细说明了两者的配置步骤、代码实现及注意事项,推荐使用 `app_links` 进行新项目开发。
66 1
|
3月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
77 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
下一篇
oss创建bucket