flutterdartrichtextlanguagetooltextspan

Flutter -> RichText From a List of Words


I don't have much experience with flutter.

I am using the language_tool library (https://pub.dev/packages/language_tool) for Dart and Flutter.

I would like the words present in the Errors list, which are the words with grammatical errors found thanks to language_tool inside the String text = 'Henlo i am Gabriele';

are in red and underlined, (and that this happens even if I decide to change the string text).

- this is the code I have so far:

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

void main() => runApp(mainApp());

class mainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Chat(),
    );
  }
}

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

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

class _ChatState extends State<Chat> {
  String text = 'Henlo i am Gabriele';
  List<WritingMistake> mistakes = [];
  List Error = [];

  void tool(String text) async {
    var tool = LanguageTool();
    var result = tool.check(text);
    var correction = await result;

    for (var m in correction) {
      WritingMistake mistake = WritingMistake(
        message: m.message,
        offset: m.offset,
        length: m.length,
        issueType: m.issueType,
        issueDescription: m.issueDescription,
        replacements: m.replacements,
      );

      mistakes.add(mistake);
    }

    for (var mistake in mistakes) {
      var error =
          text.substring(mistake.offset!, mistake.offset! + mistake.length!);
      Error.add(error);
    }

    print(mistakes.length);
    print(mistakes);
    print(Error);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: [
            Container(
              color: Colors.red,
              height: 150.0,
              width: double.infinity,
              child: Center(
                  child: Text(text, style: const TextStyle(fontSize: 20.0))),
            ),
            Container(
              color: Colors.white60,
              height: 150.0,
              width: double.infinity,
            ),
          ],
        ),
      ),
    );
  }
}

this is the output of this code:

enter image description here

and finally, this is the output I would like to have:

enter image description here

I hope I was clear and I hope someone can help me.

Thank you :)


Solution

  • This should works, but need to be refactored. I also update your function from "tool" to "getErrors" I implement the FutureBuilder because we need to work with an asynchronous process tool.check(text) that check the string and return the correction with errors.
    I can't find a way to make a linear underline, you can notice some up and down in the underline.

    EDIT: I updated the code now it's work. PS: the space between words was made manually using a SizedBox. I would be grateful if you find and share a more comfortable solution.
    I also added a string with loading text while the library is looking for some errors in the string.

    import 'package:flutter/material.dart';
    import 'package:language_tool/language_tool.dart';
    
    void main() => runApp(mainApp());
    
    class mainApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: Chat(),
        );
      }
    }
    
    class Chat extends StatefulWidget {
      const Chat({Key? key}) : super(key: key);
    
      @override
      _ChatState createState() => _ChatState();
    }
    
    class _ChatState extends State<Chat> {
      String text = 'Henlo Manuel, wher is Gabriele';
    
      Future<List<String>> getErrors(String text) async {
        List<WritingMistake> mistakes = [];
        List<String> Error = [];
        var tool = LanguageTool();
        var result = tool.check(text);
        var correction = await result;
    
        for (var m in correction) {
          WritingMistake mistake = WritingMistake(
            message: m.message,
            offset: m.offset,
            length: m.length,
            issueType: m.issueType,
            issueDescription: m.issueDescription,
            replacements: m.replacements,
          );
    
          mistakes.add(mistake);
        }
    
        for (var mistake in mistakes) {
          var error =
              text.substring(mistake.offset!, mistake.offset! + mistake.length!);
          Error.add(error);
        }
    
        print(mistakes.length);
        print(mistakes);
        print(Error);
    
        return Error;
      }
    
      // Render widget with final string
      Future<Widget> _printWordWithError(String string) async {
        // Call your function to generate some errors
        final List<String> errors = await getErrors(string);
        final List<String> wordsList = string.split(' ');
    
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ...List.generate(
              wordsList.length,
              (i) {
                final String word = wordsList[i];
                final hasSpace = i < wordsList.length - 1;
                if (errors.contains(word)) {
                  return Row(
                    children: [
                      getStyledErrorText("$word"),
                      hasSpace ? const SizedBox(width: 4) : Container()
                    ],
                  );
                } else {
                  return Row(
                    children: [
                      getStyledValidText("$word"),
                      hasSpace ? const SizedBox(width: 4) : Container()
                    ],
                  );
                }
              },
            ),
          ],
        );
      }
    
      // Render single text with error style
      Widget getStyledErrorText(String string) {
        return Text(
          string,
          style: const TextStyle(
            fontSize: 20,
            color: Colors.red,
            decorationStyle: TextDecorationStyle.solid,
            decorationColor: Colors.red,
            decoration: TextDecoration.underline,
          ),
        );
      }
    
      // Render single text without error style
      Widget getStyledValidText(String string) {
        return Text(
          string,
          style: const TextStyle(
            fontSize: 20,
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: ListView(
              children: [
                Container(
                  color: Colors.red,
                  height: 150.0,
                  width: double.infinity,
                  child: Center(
                      child: Text(text, style: const TextStyle(fontSize: 20.0))),
                ),
                Container(
                  color: const Color.fromARGB(153, 112, 112, 112),
                  height: 150.0,
                  width: double.infinity,
                  child: FutureBuilder(
                    future: _printWordWithError(text),
                    builder: (ctx, AsyncSnapshot<Widget> text) {
                      if (text.data == null) {
                        return const Center(
                          child: Text(
                            "Loading string",
                            style: TextStyle(fontSize: 20),
                          ),
                        );
                      }
                      return Center(
                        child: text.data,
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }