Integration of Huawei ML Kit Text To Speech [TTS] - Listen to your story in Flutter StoryApp

Tags: accountkit, adskit, mlkit
Published: 2022-03-04 11:24:17


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yxqtaqlik3aomv073o1g.jpeg)

Introduction

In this article, we will be integrating Huawei ML kit in Flutter StoryApp to listen stories using ML kit Text To Speech (TTS). ML Kit provides diversified leading machine learning capabilities that are easy to use, helping you to develop various AI apps. ML Kit allows your apps to easily leverage Huawei's long-term proven expertise in machine learning to support diverse artificial intelligence (AI) applications throughout a wide range of industries. 

In this flutter sample application, we are using Language/Voice-related services, services are as follows. 

**Real-time translation:** Translates text from the source language into the target language through the server on the cloud. 
**On-device translation: **Translates text from the source language into the target language with the support of an on-device model, even when no Internet service is available. 
**Real-time language detection:** Detects the language of text online. Both single-language text and multi-language text are supported. 
On-device language detection: Detects the language of text without Internet connection. Both single-language text and multi-language text are supported. 
**Automatic speech recognition:** Converts speech (no longer than 60 seconds) into text in real time.
Automatic speech recognition: Converts speech (no longer than 60 seconds) into text in real time. 
**Text to speech: **Converts text information into audio output online in real time. Rich timbres, and volume and speed options are supported to produce more natural sounds. 
**On-device text to speech:** Converts text information into speech with the support of an on-device model, even when there is no Internet connection. 
**Audio file transcription:** Converts an audio file (no longer than 5 hours) into text. The generated text contains punctuation and timestamps. Currently, the service supports Chinese and English. 
**Real-time transcription:** Converts speech (no longer than 5 hours) into text in real time. The generated text contains punctuation and timestamps. 
**Sound detection:** Detects sound events in online (real-time recording) mode. The detected sound events can help you perform subsequent actions. 
Supported Devices
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38f14r7rvwalel2lmz9q.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2j04ctt8it8ed6twrac0.png)

Development Overview

You need to install Flutter and Dart plugin in IDE and I assume that you have prior knowledge about the Flutter and Dart.

Hardware Requirements

A computer (desktop or laptop) running Windows 10.
Android phone (with the USB cable), which is used for debugging.
Software Requirements

Java JDK 1.7 or later.
Android studio software or Visual Studio or Code installed.
HMS Core (APK) 4.X or later.
Integration process

Step 1: Create Flutter project.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqy4osn1pl6t4mq38qd2.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap8qapzbwukjihe5dtyx.png)
Step 2: Add the App level gradle dependencies.

Choose inside project Android > app > build.gradle.
`apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
` 
Root level gradle dependencies
`maven {url 'https://developer.huawei.com/repo/'}
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
`
Step 3: Add the below permissions in Android Manifest file.
`<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
` 
Step 4: Download flutter plugins

Step 5: Add downloaded file into parent directory of the project. Declare plugin path in pubspec.yaml file under dependencies. Add path location for asset image.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7t3nb0rr8m48v31zrru.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fucyr4h4gmwfshzrxf8v.png)
Let's start coding
loginScreen.dart

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: LoginDemo(),
    );
  }
}

class LoginDemo extends StatefulWidget {
  @override
  _LoginDemoState createState() => _LoginDemoState();
}

class _LoginDemoState extends State<LoginDemo> {
  final HMSAnalytics _hmsAnalytics = new HMSAnalytics();
  @override
  void initState() {
    HwAds.init();
    showSplashAd();
    super.initState();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("Login Page"),
        backgroundColor: Colors.grey[850],
      ),
      body: RefreshIndicator(
        onRefresh: showToast,
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.only(top: 60.0),
                child: Center(
                  child: Container(
                      width: 200,
                      height: 150,
                      decoration: BoxDecoration(
                          color: Colors.red,
                          borderRadius: BorderRadius.circular(60.0)),
                      child: Image.asset('images/logo_huawei.png')),
                ),
              ),
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 15),
                child: TextField(
                  decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Email',
                      hintText: 'Enter valid email id '),
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(
                    left: 15.0, right: 15.0, top: 15, bottom: 0),
                child: TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Password',
                      hintText: 'Enter password'),
                ),
              ),
              FlatButton(
                onPressed: () {
                  //TODO FORGOT PASSWORD SCREEN GOES HERE
                },
                child: Text(
                  'Forgot Password',
                  style: TextStyle(color: Colors.blue, fontSize: 15),
                ),
              ),
              Container(
                height: 50,
                width: 270,
                decoration: BoxDecoration(
                    color: Colors.red, borderRadius: BorderRadius.circular(20)),
                child: FlatButton(
                  onPressed: () async {
                    try {
                      try {
                        final bool result = await AccountAuthService.signOut();
                        if (result) {
                          final bool response =
                              await AccountAuthService.cancelAuthorization();
                        }
                      } on Exception catch (e) {
                        print(e.toString());
                      }
                    } on Exception catch (e) {
                      print(e.toString());
                    }
                  },
                  child: GestureDetector(
                    onTap: () async {
                      try {
                        final bool response =
                            await AccountAuthService.cancelAuthorization();
                      } on Exception catch (e) {
                        print(e.toString());
                      }
                    },
                    child: Text(
                      'Login',
                      style: TextStyle(color: Colors.white, fontSize: 25),
                    ),
                  ),
                ),
              ),
              SizedBox(
                height: 5,
              ),
              Container(
                height: 50,
                width: 270,
                decoration: BoxDecoration(
                    color: Colors.red, borderRadius: BorderRadius.circular(20)),
                child: HuaweiIdAuthButton(
                    theme: AuthButtonTheme.FULL_TITLE,
                    buttonColor: AuthButtonBackground.RED,
                    borderRadius: AuthButtonRadius.MEDIUM,
                    onPressed: () {
                      signInWithHuaweiAccount();
                    }),
              ),
              SizedBox(
                height: 30,
              ),
              GestureDetector(
                onTap: () {
                  //showBannerAd();
                },
                child: Text('New User? Create Account'),
              ),
            ],
          ),
        ),
      ),
    );
  }
  
  void signInWithHuaweiAccount() async {
    AccountAuthParamsHelper helper = new AccountAuthParamsHelper();

    helper.setAuthorizationCode();
    try {
      // The sign-in is successful, and the user's ID information and authorization code are obtained.
      Future<AuthAccount> account = AccountAuthService.signIn(helper);
      account.then((value) => Fluttertoast.showToast(
          msg: "Welcome " + value.displayName.toString(),
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0));
      Navigator.push(
          context, MaterialPageRoute(builder: (_) => StoryListScreen()));
    } on Exception catch (e) {
      print(e.toString());
    }
  }

  Future<void> showToast() async {
    Fluttertoast.showToast(
        msg: "Refreshing.. ",
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER,
        timeInSecForIosWeb: 1,
        backgroundColor: Colors.lightBlue,
        textColor: Colors.white,
        fontSize: 16.0);
  }
  
//Show Splash Ad
  void showSplashAd() {
    SplashAd _splashAd = createSplashAd();
    _splashAd
      ..loadAd(
          adSlotId: "testq6zq98hecj",
          orientation: SplashAdOrientation.portrait,
          adParam: AdParam(),
          topMargin: 20);

    Future.delayed(Duration(seconds: 10), () {
      _splashAd.destroy();
    });
  }

  SplashAd createSplashAd() {
    SplashAd _splashAd = new SplashAd(
      adType: SplashAdType.above,
      ownerText: ' Huawei SplashAd',
      footerText: 'Test SplashAd',
    ); // Splash Ad
    return _splashAd;
  }
}

```
storyListScreen.dart

```
class StoryListScreen extends StatefulWidget {
  @override
  _StoryListScreenState createState() => _StoryListScreenState();
}
class _StoryListScreenState extends State<StoryListScreen> {
  final _itemExtent = 56.0;
  final generatedList = List.generate(22, (index) => 'Item $index');
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stories'),
      ),
      backgroundColor: Colors.white,
      body: CustomScrollView(
        controller: ScrollController(initialScrollOffset: _itemExtent * 401),
        slivers: [
          SliverFixedExtentList(
            itemExtent: _itemExtent,
            delegate: SliverChildBuilderDelegate(
              (context, index) => Card(
                margin: EdgeInsets.only(left: 12, right: 12, top: 5, bottom: 5),
                child: Center(
                  child: GestureDetector(
                    onTap: () {
                      showStory(index);
                    },
                    child: ListTile(
                      title: Text(
                        storyTitles[index],
                        style: TextStyle(
                            fontSize: 22.0, fontWeight: FontWeight.bold),
                      ),
                    ),
                  ),
                ),
              ),
              childCount: storyTitles.length,
            ),
          ),
        ],
      ),
    );
  }

  void showStory(int index) {
    print(storyTitles[index] + " Index :" + index.toString());
    Navigator.push(
        context, MaterialPageRoute(builder: (_) => StoryDetails(index)));
  }
}

```
storyDetails.dart

```
class StoryDetails extends StatefulWidget {
  int index;
  StoryDetails(this.index);
  @override
  _StoryDetailsState createState() => new _StoryDetailsState(index);
}

class _StoryDetailsState extends State<StoryDetails> {
  int index = 0;
  MLTtsEngine? engine = null;
  bool toggle = false;
  BannerAd? _bannerAd = null;

  bool isPaused = false;

  _StoryDetailsState(this.index);

  @override
  void initState() {
    // TODO: implement initState

    initML();
    showBannerAd();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: _onBackPressed,
      child: Scaffold(
          appBar: AppBar(
            title: Text(storyTitles[index]),
            actions: <Widget>[
              IconButton(
                  icon: toggle
                      ? Icon(Icons.pause_circle_filled_outlined)
                      : Icon(
                          Icons.play_circle_fill_outlined,
                        ),
                  onPressed: () {
                    setState(() {
                      // Here we changing the icon.
                      toggle = !toggle;
                      if (toggle) {
                        if (!isPaused) {
                          // do something
                          print("......Play.....");
                          final stream = storyDetails[index].splitStream(
                            chunkSize: 499,
                            splitters: [','],
                            delimiters: [r'\'],
                          );
                          play(stream);
                        } else {
                          MLTtsEngine().resume();
                          isPaused = false;
                        }
                      } else {
                        isPaused = true;
                        MLTtsEngine().pause();
                      }
                    });
                  }),
            ],
          ),
          backgroundColor: Colors.white,
          body: SafeArea(
            child: SingleChildScrollView(
              child: Padding(
                padding: EdgeInsets.only(left: 5, right: 5, top: 3, bottom: 50),
                child: Column(children: <Widget>[
                  Card(
                    child: Image.asset(
                        "images/image_0" + index.toString() + ".png"),
                  ),
                  Card(
                      child: Text(
                    storyDetails[index],
                    style: TextStyle(
                        color: Colors.black,
                        fontWeight: FontWeight.normal,
                        fontSize: 20),
                  )),
                  Center(
                    child: Image.asset(
                      "images/greeting.gif",
                      height: 320.0,
                      width: 620.0,
                    ),
                  ),
                ]),
              ),
            ),
          )),
    );
  }
  void showBannerAd() {
    _bannerAd = createBannerAd();
    _bannerAd!
      ..loadAd()
      ..show(gravity: Gravity.bottom, offset: 1);
  }
  //Create BannerAd
  static BannerAd createBannerAd() {
    BannerAd banner = BannerAd(
        adSlotId: "testw6vs28auh3",
        size: BannerAdSize.sSmart,
        adParam: AdParam());
    banner.setAdListener = (AdEvent event, {int? errorCode}) {
      print("Banner Ad event : $event " + banner.id.toString());
    };

    return banner;
  }
  Future<bool> _onBackPressed() async {
    if (_bannerAd != null) {
      _bannerAd?.destroy();
    }
    if (engine != null) {
      engine!.stop();
    }
    return true;
  }
  Future<void> playStory(String parts) async {
    // Create MLTtsConfig to configure the speech.
    final config = MLTtsConfig(
      language: MLTtsConstants.TTS_EN_US,
      synthesizeMode: MLTtsConstants.TTS_ONLINE_MODE,
      text: parts,
    );
    // Create an MLTtsEngine object.
    engine = new MLTtsEngine();

    // Set a listener to track tts events.
    engine?.setTtsCallback(MLTtsCallback(
      onError: _onError,
      onEvent: _onEvent,
      onAudioAvailable: _onAudioAvailable,
      onRangeStart: _onRangeStart,
      onWarn: _onWarn,
    ));

// Start the speech.
    await engine?.speak(config);
  }

  void _onError(String taskId, MLTtsError err) {
   
    print(err.errorMsg);
  }

  void _onEvent(String taskId, int eventId) {
    
  }

  void _onAudioAvailable(
      String taskId, MLTtsAudioFragment audioFragment, int offset) {
  
  }

  void _onRangeStart(String taskId, int start, int end) {
   
  }

  void _onWarn(String taskId, MLTtsWarn warn) {
   
  }

  Future<void> initML() async {
    MLLanguageApp().setApiKey(
        "DAED8900[p0-tu7au4ZHZuWDrR7oKps/WybCAJ0IOi7UdLfIlsIu9C4pEw0OSNA==");
  }

  Future<void> play(Stream<List<String>> stream) async {
    int i = 0;
    await for (List<String> parts in stream) {
     
      // print(parts);
      if (i == 0) {
        playStory(parts.toString());
      }
      i++;
    }
  }
}

```
**Result**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfj5qwax6i33wp5e0j4y.gif)

Tricks and Tips

Make sure that downloaded plugin is unzipped in parent directory of project.
Makes sure that agconnect-services.json file added.
Make sure dependencies are added yaml file.
Run flutter pug get after adding dependencies.
Make sure that service is enabled in agc.
Makes sure images are defined in yaml file.
Make sure that permissions are added in Manifest file.
Conclusion

In this article, we have learnt how to integrate Huawei ML kit Text to Speech in Flutter StoryApp. It supports maximum of 500 character for one request to convert Text to Speech. Once Account kit integrated, users can login quickly and conveniently sign in to apps with their Huawei IDs after granting initial access permission. Banner and Splash Ads helps you to monetize your StoryApp.

Thank you so much for reading, and also I would like to ‘thanks author for write-ups’. I hope this article helps you to understand the integration of Huawei ML Kit, Banner and Splash Ads in flutter StoryApp.

Reference

[ML Kit Text To Speech](https://developer.huawei.com/consumer/en/doc/development/hiai-Guides/ml-tts-0000001050068169)

[StoryAuthors](https://momlovesbest.com/short-moral-stories-kids)

Account Kit – [Training Video](https://developer.huawei.com/consumer/en/training/course/video/101584324766101108)

ML Kit – [Training Video](https://developer.huawei.com/consumer/en/training/course/video/101582989856737152)
[Checkout in forum]( 
 https://forums.developer.huawei.com/forumPortal/en/topic/0202817632333550025?ha_source=hms1
 )