Call a function inside a TextSpan? - Flutter

537

How to call a function when user taps on a TextSpan:

Pass a TapGestureRecognizer to recognizer property, for example:

TextSpan(
  text: "hello world",
  style: TextStyle(
    fontSize: 20.0,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
    height: 2.5,
    letterSpacing: 0.7,
  ),
  recognizer: TapGestureRecognizer()
    ..onTap = () {
      print("TextSpan is clicked.");
      // callMyFunction();
    },
); 

Edit:

Based on your newest edits in the question, the best way to approach this task is to lift up the function that determines color based on the genetic code (I'm just calling it getColorFromCode, you can call it checkdominant or whatever). This way, you can calculate "proportion" when needed.

I've made a small demo project for you to see how to call the helper function in two different widgets: one displays the proportion and the other one displays the 16 codes in various colors.

This is how it looks like:

demo

To run the demo, create a new Flutter project and replace its entire main.dart file with the code below:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final data = [
    'AAPP', 'AApP', 'aAPP', 'aApP',
    'AAPp', 'AApp', 'aAPp', 'aApp',
    'AaPP', 'AapP', 'aaPP', 'aapP',
    'AaPp', 'Aapp', 'aaPp', 'aapp',
  ];

  @override
  Widget build(BuildContext context) {
    final colors = data.map(getColorFromCode);
    final browns = colors.where((color) => color == Colors.brown).length;
    final blacks = colors.where((color) => color == Colors.black).length;
    final whites = colors.where((color) => color == Colors.white).length;

    return Scaffold(
      appBar: AppBar(title: Text('Flutter Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Proportion: $browns:$blacks:$whites'),
            Container(
              width: 200,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blue, width: 4.0),
                borderRadius: BorderRadius.circular(16),
                color: Colors.lightGreen,
              ),
              padding: EdgeInsets.all(16),
              child: RichText(
                  text: TextSpan(
                children: [
                  for (final code in data)
                    TextSpan(
                      text: '$code ',
                      style: TextStyle(
                        color: getColorFromCode(code),
                        fontFamily: 'courier',
                      ),
                    ),
                ],
              )),
            ),
          ],
        ),
      ),
    );
  }
}

Color getColorFromCode(String code) {
  if (code.contains('A') && code.contains('P')) return Colors.brown;
  if (code.contains('A') && code.contains('pp')) return Colors.black;
  if (code.contains('aa')) return Colors.white;
  return Colors.red; // not covered in the question description
}

Edit again:

If you have more than a few data, you can also refactor the business logic into a standalone widget, so you can reuse the code easily.

For example, we can pass in 3 sets of data and build something like this:

demo showing 3 sets of data

Full source code:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final data1 = ['AAPP', 'AApP', 'aAPP', 'aApP', 'AAPp', 'AApp', 'aAPp'];
  final data2 = ['AaPP', 'AapP', 'aaPP', 'aapP', 'AaPp', 'Aapp', 'aaPp'];
  final data3 = ['aaPp', 'AapP', 'aAPP', 'aApP', 'AaPp', 'aaPP'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(MyWidget.getProportion(data1)),
            MyWidget(data: data1),
            Divider(),
            Text(MyWidget.getProportion(data2)),
            MyWidget(data: data2),
            Divider(),
            Text(MyWidget.getProportion(data3)),
            MyWidget(data: data3),
          ],
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  final List<String> data;

  const MyWidget({Key? key, required this.data}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.blue, width: 4.0),
        borderRadius: BorderRadius.circular(16),
        color: Colors.lightGreen,
      ),
      padding: EdgeInsets.all(16),
      child: RichText(
          text: TextSpan(
        children: [
          for (final code in data)
            TextSpan(
              text: '$code ',
              style: TextStyle(
                color: getColorFromCode(code),
                fontFamily: 'courier',
              ),
            ),
        ],
      )),
    );
  }

  static String getProportion(List<String> data) {
    final colors = data.map(getColorFromCode);
    final browns = colors.where((color) => color == Colors.brown).length;
    final blacks = colors.where((color) => color == Colors.black).length;
    final whites = colors.where((color) => color == Colors.white).length;
    return 'Proportion: $browns:$blacks:$whites';
  }

  static Color getColorFromCode(String code) {
    if (code.contains('A') && code.contains('P')) return Colors.brown;
    if (code.contains('A') && code.contains('pp')) return Colors.black;
    if (code.contains('aa')) return Colors.white;
    return Colors.red; // not covered in the question description
  }
}
Share:
537
bilbo_bo
Author by

bilbo_bo

Updated on January 01, 2023

Comments

  • bilbo_bo
    bilbo_bo over 1 year

    I want to call a function checkProportion() inside a TextSpan. Is it possible?

    Here is the code that I want to include the function:

     child: RichText(
                text: TextSpan(
                  children: [
                    TextSpan(
                      text: widget.result + '  ',
                      style: TextStyle(
                        fontSize: 20.0,
                        fontWeight: FontWeight.bold,
                        color: checkdominantA(widget.predominant, widget.result),
                        height: 2.5,
                        letterSpacing: 0.7,
                      ),
                ),
                //...
    

    Edit:

    I want to highlight that the color configuration might change (it is not always the same proportion) as well as the TextSpans change too. The conditions are:

    • If it contains 'A'and 'P' the color is brown;
    • If it contains 'A' and 'pp' the color is black;
    • If it contains 'aa' the color is white;

    The following images are going to make it clearer:

    Example 1:

    enter image description here

    Example 2:

    enter image description here

    • WSBT
      WSBT over 2 years
      How are the color arrangement decided? For example, in your 9:3:4 case, why are those 3 in black, not any other 3?
    • bilbo_bo
      bilbo_bo over 2 years
      It's because these 3 contains 'A' and 'pp'. I listed the conditions with bullet points right before the images so you can see it better
    • WSBT
      WSBT over 2 years
      OK, it looks like you want to get the proportion from the input data, in addition to drawing 16 texts. See my updated answer.
    • bilbo_bo
      bilbo_bo over 2 years
      It seems that it's pretty close to what I want to achieve, but I didn't test the code yet. I'm just wondering about the final: data[]: in my case, each of the 16 results changes (e.g.: now the first one is AAPP but it can also be aaPp depending on the input of the user on the previous page of the app). It's just that you put each of the 16 widgets as fixed Strings that don't change. Should I write widget1, widget2... instead?
    • WSBT
      WSBT over 2 years
      If you have more than a couple of data points, it's better to refactor the business logic into a widget, to increase code reusability. I provided an example in the answer.
    • bilbo_bo
      bilbo_bo over 2 years
      I see what you mean, but what I was trying to say is that each result is a separate widget. It is organized in this way: widget.result, widget.result2, widget.result3 ... widget.result16. There are a lot of combinations and it's almost impossible to list all of them. What I was asking is if I should put final data = [widget.result, widget.result2 ... widget.result16]
    • WSBT
      WSBT over 2 years
      Yes, that should work.
    • bilbo_bo
      bilbo_bo over 2 years
      I was trying to do it like I said in my last message but here it says that The instance member 'widget' can't be accessed in an initializer.
    • bilbo_bo
      bilbo_bo over 2 years
      I tried searching for this error but none of the solutions fit in my case
    • WSBT
      WSBT over 2 years
      Without seeing your full code, I'm not sure where you are putting that line to get the error. You can try to add late in the beginning, so it's like late final data = .... If it doesn't work, you could ask a new question and post the relevant source code.
  • bilbo_bo
    bilbo_bo over 2 years
    I actually want to call the function when the TextSpan is loaded
  • bilbo_bo
    bilbo_bo over 2 years
    Is there a way to do this?
  • bilbo_bo
    bilbo_bo over 2 years
    My case is when TextSpan is loaded. The thing is that I have 16 TextSpans inside children. I have to call this function for each TextSpan (I know I should've done a list but I'm a beginner and felt no confidence). I'm a bit confused about adapting your code to mine. Could you help me with that?
  • WSBT
    WSBT over 2 years
    @bilbo_bo While I could help you with that, it might be better to take one step back and discuss what you ultimately want to achieve. There could be an XY Problem: there might be a better way to do what you want to achieve, other than using 16 TextSpans and calling functions.
  • bilbo_bo
    bilbo_bo over 2 years
    The thing is that the 16 TextSpans have specific colors (for example: there could be 9 browns, 4 blacks and 3 whites). The function that I was trying to write was to give the proportion (e.g. Proportion: 9:4:3). So I thought about calling the function for each TextSpan. Did you get it?
  • WSBT
    WSBT over 2 years
    @bilbo_bo Okay, I edited my answer based on your comments. If it's not clear enough, you can edit your question and upload a few pictures demonstrating what you want to achieve.
  • bilbo_bo
    bilbo_bo over 2 years
    Hey, I've just edited my question to make it more clear.