Flutter change state from StatefulWidget object

I wrote my own example similar to yours. What I do here is:

Initial star rate is -1 because arrays start from 0 ;) and I create stars with position, current star rate and the callback function. We will use this callback function to update the value in the ScreenOne.

In Star widget, we have a local bool selected with default value false and we assign it a value inside the build function based on the position of the star and current rate. And we have setSelected() function which runs the callback function and updates currentRate with the value of star position.

Check the video example here.

class ScreenOne extends StatefulWidget {
  @override
  _ScreenOneState createState() => _ScreenOneState();
}

class _ScreenOneState extends State<ScreenOne> {
  int currentRate = -1; //since array starts from 0, set non-selected as -1
  List<Star> starList = []; //empty list
  @override
  void initState() {
    super.initState();
    buildStars(); //build starts here on initial load
  }

  Widget buildStars() {
    starList = [];
    for (var i = 0; i < 5; i++) {
      starList.add(Star(
        position: i,
        current: currentRate,
        updateParent: refresh, //this is callback
      ));
    }
  }

  refresh(int index) {
    setState(() {
      currentRate = index; //update the currentRate
    });
    buildStars(); //build stars again
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("Test page 1"),
      ),
      body: Container(
        child: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: starList,
          ),
        ),
      ),
    );
  }
}






class Star extends StatefulWidget {
  final Function(int index) updateParent; //callback
  final int position; //position of star
  final int current; //current selected star from parent

  const Star({Key key, this.position, this.updateParent, this.current})
      : super(key: key);

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

class _StarState extends State<Star> {
  bool selected = false;

  void setSelected() {
    widget.updateParent(widget.position);
  }

  @override
  Widget build(BuildContext context) {
    if (widget.current >= widget.position) {
      selected = true;
    } else {
      selected = false;
    }
    return GestureDetector(
      child: AnimatedCrossFade(
        firstChild: Icon(Icons.star_border),
        secondChild: Icon(Icons.star),
        crossFadeState:
            selected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
        duration: Duration(milliseconds: 300),
      ),
      onTap: () {
        setSelected();
      },
    );
  }
}

The Flutter way is to rebuild widgets whenever it is necessary. Don't be afraid to build widgets, they are cheap for the SDK, specially in this case for simple stars.

Accessing another widget state requires more work than just rebuilding it. To access the state you should use keys or you should add special methods in the widget itself.

In this case, where the star is rebuilt no matter what, it is even better and simpler to use plain stateless widgets because the selected state can be provided by the parent in the moment of rebuilding.

And since the state is stored in the parent widget, I think it is better no to store it as wall in each one of the individual stars.

Next is a very simple solution that follows that idea. But yes, it still rebuilds the stars.

import 'package:flutter/material.dart';

import 'package:font_awesome_flutter/font_awesome_flutter.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(body: Center(child: StarRatingWidget())),
    );
  }
}

class StarRatingWidget extends StatefulWidget {
  @override
  _StarRatingWidgetState createState() {
    return _StarRatingWidgetState();
  }
}

class _StarRatingWidgetState extends State<StarRatingWidget> {
  int _currentRating = 0;

  List<Widget> buildStars() {
    List<RatingStarWidget> starWidgets = [];
    for (int i = 0; i < 5; i++) {
      starWidgets.add(
        RatingStarWidget(
          clickCallback: () => setState(() {
                _currentRating = i + 1;
              }),
          highlighted: _currentRating > i,
        ),
      );
    }
    return starWidgets;
  }

  @override
  Widget build(BuildContext buildContext) {
    return Row(
      children: buildStars(),
    );
  }
}

class RatingStarWidget extends StatelessWidget {
  //Properties
  final VoidCallback clickCallback;
  final bool highlighted;

  //Constructors
  RatingStarWidget({this.clickCallback, this.highlighted});

  @override
  StatelessElement createElement() {
    print("Element created");
    return super.createElement();
  }

  //Methods
  @override
  Widget build(BuildContext buildContext) {
    return GestureDetector(
      onTap: () {
        clickCallback();
      },
      child: AnimatedCrossFade(
        firstChild: Icon(
          FontAwesomeIcons.star,
          size: 14,
        ),
        secondChild: Icon(
          FontAwesomeIcons.solidStar,
          size: 14,
        ),
        duration: Duration(milliseconds: 300),
        crossFadeState:
            highlighted ? CrossFadeState.showSecond : CrossFadeState.showFirst,
      ),
    );
  }
}