Не обновляется текст при нажатии на кнопку

Имеется 2 виджета: MyHomePage и Buttons. Где в MyHomePage должна отображаться информация из List'а, а Buttons просто как отдельный "кастомный виджет". Во время того, как я нажимаю на кнопку у меня должен менятся id для того, чтобы определить какой итем из List'a нужно отобразить в MyHomePage.

class _ButtonsState extends State<Buttons> {
  var s = Logic();
  void _func(int id) {
    setState(() {
      s.id = id;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MaterialButton(
          onPressed: () => _func(1),
          child: Text('Text 1'),
        ),
        MaterialButton(
          onPressed: () => _func(2),
          child: Text('Text 2'),
        ),
      ],
    );
  }
}

Но при нажатии на кнопку информация не обновляется, хотя в MyHomePage есть StreamBuilder

class _MyHomePageState extends State<MyHomePage> {
  var s = Logic();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StreamBuilder<String>(
              stream: s.stream(),
              builder: (context, snapshot) {
                if (snapshot.data != null && snapshot.hasData) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                } else {
                  return Container(
                    width: 100,
                    height: 100,
                    color: Colors.red,
                  );
                }
              },
            ),
            Buttons(),
          ],
        ),
      ),
    );
  }
}

Вот код самого Stream на который я подписывался в StreamBuilder

class Logic {
  int? id;
  var text = ListStrings();
  Stream<String>? stream() {
    if (id == 1) {
      return Stream.value(text.text[0]);
    } else if (id == 2) {
      return Stream.value(text.text[1]);
    } else {
      return null;
    }
  }
}

сам id в классе Logic обновляется при нажатии на кнопку. В чем заключается ошибка?


Ответы (1 шт):

Автор решения: ddo5
  1. В вашем примере Logic.stream возвращает стрим с единственным значением, которое никак не может обновиться. То есть это эквивалентно следующему:
class Logic {
  int? id;
  var text = ListStrings();
  String? getText() {
    if (id == 1) {
      return text.text[0];
    } else if (id == 2) {
      return text.text[1];
    } else {
      return null;
    }
  }
}

class _MyHomePageState extends State<MyHomePage> {
  var s = Logic();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              ${s.getText()}',
              style: Theme.of(context).textTheme.headline4,
            ),
            Buttons(),
          ],
        ),
      ),
    );
  }
}
  1. Сам факт того, что var s в _ButtonsState и var s в _MyHomePageState - это два разных объекта, уже не позволит реализовать правильную логику.

Могу предложить следующий способ решения проблемы:

class MyHomePage extends StatefulWidget {
  MyHomePage({required this.title});

  final String title;

  @override
  State<StatefulWidget> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // Объявляем s только в одном месте
  final s = Logic();

  @override
  void dispose() {
    // Закрываем контроллер
    s.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StreamBuilder<String?>(
              stream: s.textStream,
              builder: (context, snapshot) {
                if (snapshot.data != null && snapshot.hasData) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                } else {
                  return Container(
                    width: 100,
                    height: 100,
                    color: Colors.red,
                  );
                }
              },
            ),
            Buttons(s: s),
          ],
        ),
      ),
    );
  }
}

class Buttons extends StatefulWidget {
  Buttons({required this.s});

  final Logic s;

  @override
  State<StatefulWidget> createState() => _ButtonsState();
}

class _ButtonsState extends State<Buttons> {
  void _func(int id) {
    // Теперь при задании id, новое значение будет приходить в _idController
    widget.s.id = id;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MaterialButton(
          onPressed: () => _func(1),
          child: Text('Text 1'),
        ),
        MaterialButton(
          onPressed: () => _func(2),
          child: Text('Text 2'),
        ),
      ],
    );
  }
}

class Logic {
  final text = ListStrings();

  int? _id;

  int? get id => _id;

  set id(int? newValue) {
    _id = newValue;
    _idController.add(newValue);
  }

  final _idController = StreamController<int?>();

  Stream<String?> get textStream {
    // Трансформируем стрим для того, чтобы получать не id, а значение из списка text.text
    return _idController.stream.map<String?>(
      (id) {
        if (id == null) return null;
        final textIndex = id - 1;
        // Если индекс не входит в границы списка, возвращаем null
        if (textIndex < 0 || textIndex >= text.text.length) return null;
        return text.text[textIndex];
      },
    );
  }

  void dispose() {
    _idController.close();
  }
}
→ Ссылка