Closed9

Flutterでアプリ作るメモ

ピン留めされたアイテム
いけぐまナトリウムいけぐまナトリウム

スクラップ概要

Flutterアプリの制作過程を備忘録的に記録したもの。
簡単なアプリを作成予定

作るもの

概要

英語の発音を練習するためのスマホアプリ

使い方の想定

  1. ユーザがスマホに英語で話しかける
  2. アプリはユーザの英語を文字起こし(ここでちゃんと文字起こしできるかで、ユーザは英語の発音が正しいかどうかを確認できる)
    というシンプルなアプリ。

追加機能として、英会話モードを考え中。
英会話モードでは、ユーザの音声入力に対してAIが返答を考えて送信。それに対して更にユーザが話して……というものを考えている。

返答にはML Kitのスマートリプライを使おうと考え中。

また、英会話モードを追加することも考えると、UIはチャットアプリのようなものを想定

いけぐまナトリウムいけぐまナトリウム

ここ数日分のやつをまとめて投稿

Flutterで英語音声の文字起こしをする。
pub.devにあるspeech_to_textを使う。

入力音声の言語は本体設定の言語に依存している。
今回は本体設定の言語関係無く英語入力にしたいので、以下のコードをlisten部分辺りに書く

    await _speechToText.listen(
      onResult: _onSpeechResult,
      localeId: "en_US",
    );

この使用可能なlocalIdの一覧がどこから見ることができるのか不明。
公式のSwitching Recognition Languageに載っているlocales()関数はその本体設定の言語を取得するっぽい??

英語にしたかったので、適当にen_USにする。
speech_to_textがどこまで対応しているかは分からないが、このlocaleIdの表記はGCPのSpeechToTextのBCP-47と同じっぽい。


これからやることメモ

  • 最低限表示させるためのUIを作る
  • 返信を表示させる
  • 本格的なUIの作り込み

一週間で終わるかなぁ…?

いけぐまナトリウムいけぐまナトリウム

Flutterで簡単に使えそうなチャットUIは、flutter_chat_uichat_bubblesの二つだと感じた。
今回のアプリではchat_bubblesの方を採用。その理由も含めて、これらを軽く紹介する。

flutter_chat_ui

メリット

  • 本格的な見た目のチャットUIが簡単に作れる
    • exampleだけで簡単なチャットアプリになる
  • デザインが綺麗

デメリット

  • 画面構成の自由度が低い(自分が使い方を分かっていなかっただけの可能性もある)
    • 今回の場合、入力が音声なので入力用のテキストボックスが不要なのだが、消したり他のボタンに変更する方法が分からなかった
    • 入力欄をタップした時に何かしらのアクションをする機能が無かった

chat_bubbles

メリット

  • 画面構成の自由度が高い
    • チャットのバブル部分(メッセージを表示する所)だけ使うことが可能

デメリット

  • exampleがflutter_chat_uiに比べてやや不親切
    • 甘えなのは分かっているが、画面にバブルを追加する関数も置いて欲しかった……
  • デザイン性が作り手次第
    • exampleそのまま使うのは少し微妙なデザインしている

今回はチャットの入力欄が不要で、バブル部分のみ使いたかったので、chat_bubblesの方を採用

いけぐまナトリウムいけぐまナトリウム

前述のchat_bubblesを使って、UI部分のみを作っていく
配色はColor Huntというサイトのこの色を採用。

完成イメージ

完成イメージのスクリーンショット

コード

事前準備

  • 空のFlutterアプリを作成
  • chat_bubblesをインストール
    • flutter pub add chat_bubblesコマンドを実行

コード構成

libの下にmain.dartbubbles.dartを入れる。

コード(長いので折りこみ)

main.dart
main.dart
import 'package:flutter/material.dart';
import 'bubbles.dart';
import 'package:chat_bubbles/chat_bubbles.dart';

void main() {
  // runApp関数でウィジェットツリーのルートとなる。
  runApp(const App());
}

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

  // stateLessWidget または stateFulWidgetを継承した場合は必ずbuildメソッドをオーバーライドしないといけない。
  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomeScreen(),
    );
  }
}

// 途中で値が変わったらそれを反映させる必要があるのでStatefulWidgetを記載する
class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

// StatefulWidgetはWidgetにstateの概念をいれて拡張したもの
// StatefulWidgetはcreateStateメソッドを持ち、これがStateクラスを返す
  
  ////stateを継承している型
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // メッセージを格納するための可変長リストを作成
  final _bubbles = List.empty(
    growable: true,
  );

  
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: const Color.fromARGB(255, 255, 255, 255), //背景色
        //タイトルバー
        appBar: AppBar(
          title: const Text('English Practice'),
          backgroundColor: const Color(0xFF96B6C5),
        ),
        body: ListView.builder(
          //生成する個数を指定する
          itemCount: _bubbles.length,

          // ↓itemBuilderの関数型の定義 Widget型を返す
          // ここで再描画用のBubbleSpecialThreeをbubblesの個数だけ作成する
          itemBuilder: (BuildContext context, int index) {
            // 自分の発言の場合、青背景に白文字
            if (_bubbles[index].isSender) {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFF96B6C5),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFFF4F2DE), fontSize: 20),
              );
            }
            // 自分以外の発言の場合、白背景に青文字
            else {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFFF4F2DE),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFF96B6C5), fontSize: 20),
              );
            }
          },
        ),
        floatingActionButton: FloatingActionButton(
          backgroundColor: const Color(0xFFE9B384),
          onPressed: () {
            // setState()を呼び出すことで裏では再度buildメソッドを呼び出して変更された変数で再描画している
            setState(() {
              // 自分のチャットバブル
              _bubbles.add(
                Bubbles("me", true),
              );
              // 自分以外のチャットバブル
              _bubbles.add(
                Bubbles("you", false),
              );
            });
          },
          child: const Icon(Icons.mic),
        ));
  }
}
bubbles.dart
bubbles.dart
class Bubbles {
  String message;
  bool isSender;

  Bubbles(this.message, this.isSender);
}

次すること

右下のボタンを押せば音声入力がONになり、音声で聞き取った内容を画面上に表示するようにする

いけぐまナトリウムいけぐまナトリウム

Speech to textのlisten関数のパラメータ日本語メモ。
自分用なので、自分がいじった部分しか載せていない。他にも色々あるので、気になる場合は下リンクよりどうぞ。
参考元

  • onResult:単語が認識されたときに呼び出されるメソッドを書く
  • parceFor:単語が聞こえなくなってから何秒間、音声認識状態であるのかを設定する。
  • localeId:どこの国の何語を聞き取るか設定する
いけぐまナトリウムいけぐまナトリウム

前述のソースコードを変更し、音声認識の結果をチャットに表示する。

コード

前提

  • 前述のbubbles.dartは変更なし。
  • speech_to_textをインストール
    • flutter pub add speech_to_text

変更部分

  • main.dart
  • ios\Runner\Info.plist
  • android\app\src\main\AndroidManifest.xml

コード

Info.plistとAndroidManifest.xmlはspeech_to_textのPermission部分とやっていることが同じです。

Info.plist

iosで音声認識を使用するための設定。

Info.plist(一部のみ)
Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
	<!--変更部分ここから-->
	<key>NSSpeechRecognitionUsageDescription</key>
	<string>This permission is required to use voice analysis.</string>
	<key>NSMicrophoneUsageDescription</key>
        <string>Need microphone access for voice recognition</string>
	<!--変更部分ここまで-->

以下略(デフォルトから変更なし)

AndroidManifest.xml

androidで音声認識を使用するための設定。

AndroidManifest.xml
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--変更部分ここから-->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
    <!--変更部分ここまで-->
    <application 以下略(デフォルトから変更なし)

main.dart

Widget内はfloatingActionButton部分のみ変更。
あとはWidgetより上部分に関数、変数をいくつか追加。

main.dart
main.dart
import 'package:flutter/material.dart';
import 'bubbles.dart';
import 'package:chat_bubbles/chat_bubbles.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';

void main() {
  // runApp関数でウィジェットツリーのルートとなる。
  runApp(const App());
}

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

  // stateLessWidget または stateFulWidgetを継承した場合は必ずbuildメソッドをオーバーライドしないといけない。
  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomeScreen(),
    );
  }
}

// 途中で値が変わったらそれを反映させる必要があるのでStatefulWidgetを記載する
class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

// StatefulWidgetはWidgetにstateの概念をいれて拡張したもの
// StatefulWidgetはcreateStateメソッドを持ち、これがStateクラスを返す
  
  ////stateを継承している型
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // メッセージを格納するための可変長リストを作成
  final _bubbles = List.empty(
    growable: true,
  );
  final SpeechToText _speechToText = SpeechToText(); // 音声認識をする
  String _lastWords = ''; // 認識結果を格納

  //アプリ起動時に実行される
  
  void initState() {
    super.initState();
    _initSpeech();
  }

  // speechToTextを初期化する
  void _initSpeech() async {
    await _speechToText.initialize();
  }

  /// 音声認識が開始されると呼び出される
  void _startListening() async {
    //TODO: speechtotextnotinitializedexceptionのキャッチ
    await _speechToText.listen(
      onResult: _onSpeechResult, // 単語が認識されると呼び出される
      pauseFor: const Duration(seconds: 3), // 何秒音声が聞こえなかったら終了するかを設定
      localeId: "en_US", // 認識言語を英語(アメリカ)にする
    );
  }

  // 音声認識の終了ボタンが押されると呼び出される
  void _stopListening() async {
    await _speechToText.stop();
  }

  // 認識した単語を処理する
  void _onSpeechResult(SpeechRecognitionResult result) {
    setState(() {
      // 最終結果が取得できた場合
      if (result.finalResult) {
        // 認識した単語を取得
        _lastWords = result.recognizedWords;

        // 何かしらの単語を取得したら、チャット表示
        if (_lastWords != '') {
          _bubbles.add(
            Bubbles(_lastWords, true),
          );
        }
      }
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: const Color.fromARGB(255, 255, 255, 255), //背景色
        //タイトルバー
        appBar: AppBar(
          title: const Text('English Practice'),
          backgroundColor: const Color(0xFF96B6C5),
        ),
        body: ListView.builder(
          //生成する個数を指定する
          itemCount: _bubbles.length,

          // ↓itemBuilderの関数型の定義 Widget型を返す
          // ここで再描画用のBubbleSpecialThreeをbubblesの個数だけ作成する
          itemBuilder: (BuildContext context, int index) {
            // 自分の発言の場合、青背景に白文字
            if (_bubbles[index].isSender) {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFF96B6C5),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFFF4F2DE), fontSize: 20),
              );
            }
            // 自分以外の発言の場合、白背景に青文字
            else {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFFF4F2DE),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFF96B6C5), fontSize: 20),
              );
            }
          },
        ),
        floatingActionButton: FloatingActionButton(
          backgroundColor: const Color(0xFFE9B384),
          //マイクボタンが押されたとき、Listen状態でなければstartListeninig、そうでなければstopListeningする
          onPressed:
              _speechToText.isNotListening ? _startListening : _stopListening,
          child: Icon(_speechToText.isNotListening ? Icons.mic_off : Icons.mic),
        ));
  }
}

次すること

自動返信機能を追加する

いけぐまナトリウムいけぐまナトリウム

やったのだいぶ前であんまり覚えていないので、書き漏れあったらすみません。

コード

前提

  • 前述のbubbles.dartは変更なし。
  • main.dartと同じフォルダにreply.dartを追加
  • google_mlkit_smart_replyをpub add
    • flutter pub add google_mlkit_smart_reply

変更部分

  • main.dart
  • reply.dart(新規)

コード

main.dart

恐らく_onSpeechResult()のcreateReplyの部分だけ変更したと思う。

main.dart
main.dart
import 'package:flutter/material.dart';
import 'bubbles.dart';
import 'reply.dart';
import 'package:chat_bubbles/chat_bubbles.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';

void main() {
  // runApp関数でウィジェットツリーのルートとなる。
  runApp(const App());
}

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

  // stateLessWidget または stateFulWidgetを継承した場合は必ずbuildメソッドをオーバーライドしないといけない。
  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomeScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

// 途中で値が変わったらそれを反映させる必要があるのでStatefulWidgetを記載する
class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

// StatefulWidgetはWidgetにstateの概念をいれて拡張したもの
// StatefulWidgetはcreateStateメソッドを持ち、これがStateクラスを返す
  
  ////stateを継承している型
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // メッセージを格納するための可変長リストを作成
  final _bubbles = List.empty(
    growable: true,
  );
  final SpeechToText _speechToText = SpeechToText(); // 音声認識をする
  String _lastWords = ''; // 認識結果を格納

  //アプリ起動時に実行される
  
  void initState() {
    super.initState();
    _initSpeech();
  }

  // アプリ終了時に実行される
  
  void dispose() {
    disposeReply();
    super.dispose();
  }

  // speechToTextを初期化する
  void _initSpeech() async {
    await _speechToText.initialize();
  }

  /// 音声認識が開始されると呼び出される
  void _startListening() async {
    //TODO: speechtotextnotinitializedexceptionのキャッチ
    await _speechToText.listen(
      onResult: _onSpeechResult, // 単語が認識されると呼び出される
      pauseFor: const Duration(seconds: 3), // 何秒音声が聞こえなかったら終了するかを設定
      localeId: "en_US", // 認識言語を英語(アメリカ)にする
    );
  }

  // 音声認識の終了ボタンが押されると呼び出される
  void _stopListening() async {
    await _speechToText.stop();
  }

  // 認識した単語を処理する
  void _onSpeechResult(SpeechRecognitionResult result) async {
    // 最終結果が取得できた場合
    if (result.finalResult) {
      // 認識した単語を取得
      _lastWords = result.recognizedWords;

      // 何かしらの単語を取得した場合
      if (_lastWords != '') {
        // 自分の発言としてチャットを表示
        setState(() {
          _bubbles.add(
            Bubbles(_lastWords, true),
          );
        });
        // 自動返信を作成する
        final reply = await createReply(_lastWords);
        // 相手の発言としてチャットを表示
        setState(() {
          _bubbles.add(Bubbles(reply, false));
        });
      }
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: const Color.fromARGB(255, 255, 255, 255), //背景色
        //タイトルバー
        appBar: AppBar(
          title: const Text('English Practice'),
          backgroundColor: const Color(0xFF96B6C5),
        ),
        body: ListView.builder(
          //生成する個数を指定する
          itemCount: _bubbles.length,

          // ↓itemBuilderの関数型の定義 Widget型を返す
          // ここで再描画用のBubbleSpecialThreeをbubblesの個数だけ作成する
          itemBuilder: (BuildContext context, int index) {
            // 自分の発言の場合、青背景に白文字
            if (_bubbles[index].isSender) {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFF96B6C5),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFFF4F2DE), fontSize: 20),
              );
            }
            // 自分以外の発言の場合、白背景に青文字
            else {
              return BubbleSpecialThree(
                text: _bubbles[index].message,
                isSender: _bubbles[index].isSender,
                color: const Color(0xFFF4F2DE),
                tail: true,
                textStyle:
                    const TextStyle(color: Color(0xFF96B6C5), fontSize: 20),
              );
            }
          },
        ),
        floatingActionButton: FloatingActionButton(
          backgroundColor: const Color(0xFFE9B384),
          //マイクボタンが押されたとき、Listen状態でなければstartListeninig、そうでなければstopListeningする
          onPressed:
              _speechToText.isNotListening ? _startListening : _stopListening,
          child: Icon(_speechToText.isNotListening ? Icons.mic_off : Icons.mic),
        ));
  }
}

reply.dart

androidで音声認識を使用するための設定。

reply.dart
reply.dart
import 'package:google_mlkit_smart_reply/google_mlkit_smart_reply.dart';

final SmartReply _smartReply = SmartReply();

void disposeReply() {
  _smartReply.close();
}

// 自動返信メッセージを作成
Future<String> createReply(String myMessage) async {
  // ユーザから送られたメッセージを追加する
  _smartReply.addMessageToConversationFromRemoteUser(
      myMessage, DateTime.now().millisecondsSinceEpoch, 'ai');

  // 返信候補を取得
  final result = await _smartReply.suggestReplies();
  // 大きさ3のリストで返ってくるので、とりあえず先頭だけを取得
  String reply = result.suggestions[0];

  // ユーザに送られたメッセージを追加する
  _smartReply.addMessageToConversationFromLocalUser(
      reply, DateTime.now().millisecondsSinceEpoch);

  return reply;
}

次すること

機能としては完成!
次はapkファイルを作って配布できるようにする

いけぐまナトリウムいけぐまナトリウム

数人かとアプリを共有するためにリリースしたかったのだが、Playストアで共有するためには、内部アプリ共有でも25ドルかかる。
今後、あまりアプリを作って公開する予定は無いので、25ドルをケチって、apkファイルで共有することにした。

1. アイコンの設定

このリンク先の説明を参考に、以下を行う。

  • アイコン設定
  • デバッグ用バナー削除

2. 証明書を作成する

  1. 以下コマンドをコンソールに入力する。指定した出力先パスにjksファイルが出力される。
keytool -genkey -v -keystore {出力先のパス}\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload
  1. 以下のような情報を聞かれる画面が出てくるので、何も入力せずにEnterを押す。最後の確認のみyを入力。
    ※Playストア等に公開する場合はちゃんと入力した方が良いと思います。
キーストアのパスワードを入力してください:{パスワードを入力}
新規パスワードを再入力してください:{上と同じパスワードを入力}
姓名は何ですか。
  [Unknown]:  {Enter}
組織単位名は何ですか。
  [Unknown]:  {Enter}
組織名は何ですか。
  [Unknown]:  {Enter}
都市名または地域名は何ですか。
  [Unknown]:  {Enter}
都道府県名または州名は何ですか。
  [Unknown]:  {Enter}
この単位に該当する2文字の国コードは何ですか。
  [Unknown]:  {Enter}
CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknownでよろしいですか。
  [いいえ]:  y

10,000日間有効な2,048ビットのRSAのキー・ペアと自己署名型証明書(SHA256withRSA)を生成しています
        ディレクトリ名: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
<key>のキー・パスワードを入力してください
        (キーストアのパスワードと同じ場合はRETURNを押してください):{ENTER}
  1. 作成されたupload-keystore.jksは、android/app/upload-keystore.jksに置く。

3. プロパティファイルの作成

以下のファイルを作成し、android直下に置く。
パスワードの部分には、どちらも証明書作成の際に使用したパスワードを入力する。
keyAliasには、証明書の作成に用いたコマンドの最後、-alias <値>の値を入力(今回の場合upload)。

android\key.properties
storePassword={pass}
keyPassword={pass}
keyAlias=upload
storeFile=./upload-keystore.jks

4. build.gradleの設定

android/appのbuild.gradleを以下のように編集。
android直下のbuild.gradleではないので注意。

android/app/build.gradle
//先頭に記載
+def keystoreProperties = new Properties()
+def keystorePropertiesFile = rootProject.file('key.properties')
+if (keystorePropertiesFile.exists()) {
+    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}

/* 中略 */

android {
    /* 中略 */
+   signingConfigs {
+       release {
+           keyAlias keystoreProperties['keyAlias']
+           keyPassword keystoreProperties['keyPassword']
+           storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
+           storePassword keystoreProperties['storePassword']
+       }
+   }
   buildTypes {
       release {
-          signingConfig signingConfigs.debug
+          signingConfig signingConfigs.release    
       }
   }
/* 中略 */
}

5. アプリ名の設定

android/app/src/main/AndroidManifest.xmlを編集して、アプリの名前を変更する。

android/app/src/main/AndroidManifest.xml
<!-- 関連する部分のみ抜粋 -->
   <application
-        android:label="{アプリ作成時のデフォルトのプロジェクト名}"
+        android:label="{アプリ名}"

6. APKを構築する

プロジェクトフォルダで、以下のコマンドを実行

flutter build apk --split-per-abi

上手くいけば、以下の三つが作成される。

  • [project]/build/app/outputs/apk/release/app-armeabi-v7a-release.apk
  • [project]/build/app/outputs/apk/release/app-arm64-v8a-release.apk
  • [project]/build/app/outputs/apk/release/app-x86_64-release.apk

7. APKをAndroidにインストールする

様々な方法があるが、今回はGoogle Drive経由で配布した。
方法については、リンク先が詳しいので省略。
補足としては、

  • androidだと基本的にarmと書かれているapkファイル二つのどちらかでインストールできる
  • インストール画面で、セキュリティ関係の警告が出る場合があるので、その場合無視してインストールボタンを押す(詳細を表示したらインストールボタンが出てくるパターンもある)
  • セキュリティ的には微妙なので、本当に顔見知りにちょっと共有したいときじゃなければオススメしない。

参考文献


以上でインストールまで完了です。
気力があれば、リリース過程で遭遇したエラーとその対処法についてもまとめたい。

いけぐまナトリウムいけぐまナトリウム

jksファイルの位置が違う

出てきたエラー

PS {プロジェクト場所}\english_practice> flutter build apk --split-per-abi


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:validateSigningRelease'.
> Keystore file '{プロジェクト場所}\english_practice\android\app\{.\upload-keystore.jks}' not found for signing config 'release'.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 4s
Running Gradle task 'assembleRelease'...                            5.3s
Gradle task assembleRelease failed with exit code 1

原因

  • jskファイルがandroid\app直下に無い

jskファイルの内容が違う

エラー内容

PS {プロジェクト場所}\english_practice> flutter build apk --split-per-abi


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:packageRelease'.
> Multiple task action failures occurred:
   > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
      > com.android.ide.common.signing.KeytoolException: Failed to read key key from store "{プロジェクト場所}\english_practice\android\app\upload-keystore.jks": No key with alias 'key' found in keystore {プロジェクト場所}\english_practice\android\app\upload-keystore.jks
   > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
      > com.android.ide.common.signing.KeytoolException: Failed to read key key from store "{プロジェクト場所}\english_practice\android\app\upload-keystore.jks": No key with alias 'key' found in keystore {プロジェクト場所}\english_practice\android\app\upload-keystore.jks
   > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
      > com.android.ide.common.signing.KeytoolException: Failed to read key key from store "{プロジェクト場所}\english_practice\android\app\upload-keystore.jks": No key with alias 'key' found in keystore {プロジェクト場所}\english_practice\android\app\upload-keystore.jks

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 35s
Running Gradle task 'assembleRelease'...                           36.2s
Gradle task assembleRelease failed with exit code 1

原因

  • jskファイルの中のkeyAliasの値が、jskファイル生成コマンドの-alias <値>の値と一致しない
このスクラップは2023/08/28にクローズされました