Andreas
Andreas

Reputation: 51

Flutter Navigator.push -> second page does not refresh state with new values

I have a simple post module in my app, that shows "hearts" on a post overview and on a post detail page. On both pages the user can "heart" a post, doing so writes the userID back to a firestore document and increases the overall heart counter.

The problem is that if the post is hearted on the overview page and then the user navigates to the detail page, the new count and the new "hearted" status is not shown. It seems that the State is not newly set after navigating to the next page. Variables seem to be correct as I have tested with various print statements. Once I manually tap the heart again, the correct counter and heart status are shown.

How can I thus force to setState after a navigator.push command?

Navigation from the first page:

GestureDetector(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => new PostPage(postData: widget.postData)));
      }, [...] )

Hearts component - separate .dart file that is being included in both post overview and post detail pages:

class _PostStatsState extends State<PostStats> {

  var _heartsIcon = Icons.favorite;
  dynamic _hearts = -1;
  dynamic _setState = false;

  Future<String> getUserId() async {
      final prefs = await SharedPreferences.getInstance();
      return prefs.getString('userId');
  }

  @override
  Widget build(BuildContext context) {

    final PostModel postData = InheritedPostModel.of(context).postData;

    void setHeartsIcon()async{
      final userId = await getUserId();
      if (postData.heartedBy.contains(userId)) {
        _heartsIcon = Icons.favorite;
      }
      else {
        _heartsIcon = Icons.favorite_border;
      }
    }
      _hearts = widget.hearts;
      setHeartsIcon();
      print("!Set $_hearts $_heartsIcon"); // correct number and icon are printed
[...]

Widget component: // does not show the same number and icon as in print above

[...]

_showStat(
            _heartsIcon,
            _hearts,
            Colors.red[300],
            _heartOrUnheart
            ),
[...]

Widget

Widget _showStat(IconData icon, int number,Color color, Function buttonAction) {
    return Column(
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.only(right: 2.0),
          child: IconButton(icon: Icon(icon), color: color,  onPressed: buttonAction,
          enableFeedback: true,),
        ),
        Text(number.toString()),
      ],
    );
  }

I looked around and spent too many hours fixing this with no luck. Hopefully, someone has a simple solution for this.

Upvotes: 1

Views: 1238

Answers (2)

Andreas
Andreas

Reputation: 51

  @override
  Widget build(BuildContext context) {

    return FutureBuilder<void>(
        future: _setHeartsIcon(), // async work
        builder: (context, snapshot) {
           if (snapshot.connectionState == ConnectionState.done) {
            return _showStats();
          }
          else {
            // Otherwise, display a loading indicator.
            return _showStats();
          }
         },
    );
  }

This might not be the cleanest code but it ensures that _showStats() is called again after all data has been loaded. Since data only is pulled asynchronously from SharedPreferences this seems to be a matter of milliseconds and not really visible on the UI.

Upvotes: 0

Sirus
Sirus

Reputation: 143

You can set data in initState but you are using InheritedWidgets so you should set them in didChangeDependencies like this:

class _PostStatsState extends State<PostStats> {
  var _heartsIcon = Icons.favorite;
  dynamic _hearts = -1;
  dynamic _setState = false;

  Future<String> getUserId() async {
     final prefs = await SharedPreferences.getInstance();
     return prefs.getString('userId');
  }

  void setHeartsIcon(PostModel postData)async{
    final userId = await getUserId();
    if (postData.heartedBy.contains(userId)) {
      _heartsIcon = Icons.favorite;
    }
    else {
      _heartsIcon = Icons.favorite_border;
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final PostModel postData = InheritedPostModel.of(context).postData;
    _hearts = widget.hearts;
    setHeartsIcon(postData);
    print("!Set $_hearts $_heartsIcon");
  }

  @override
  Widget build(BuildContext context) {

  [....]

Always try to keep your build method as clean as possible.

Alternative method could be take passing data from previous page to this page and setting it in initState, if you are not using Inhertied widget except for this.

Upvotes: 1

Related Questions