fluttermaterial-designratingbar

How to save User rating in flutter rating bar?


Im trying to saving user rating to displaying it when user comes back to page. But im a lit struggling cannot figure out how to do this. Rating works but as I said the saving not . So what happens is that its always empty. What I actually want is that if user comes back to the page he see his rating and if he rate again and the rating is different the last rating I let him rating and if not then not and if he press clear the rating will be deleting what also works fine.

Maybe anyone can help.

lass Ratingpage extends StatefulWidget {
  final int maximumRating;
  final Function(int) onRatingSelected;

  Ratingpage(this.onRatingSelected, [this.maximumRating = 5]);

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

class _RatingpageState extends State<Ratingpage> {
  int haveusercurrentchoice;

  int _currentRating = 0;

  Widget _buildRatingStar(int index) {
    if (index < _currentRating) {
      return Icon(
        Icons.star,
        color: Colors.yellow,
      );
    } else {
      return Icon(
        Icons.star,
        color: Colors.white,
      );
    }
  }

  Widget _buildBody() {
    final stars = List<Widget>.generate(this.widget.maximumRating, (index) {
      return Expanded(
        child: GestureDetector(
          child: _buildRatingStar(index),
          onTap: () {
            setState(() {
              _currentRating = index;
            });

            this.widget.onRatingSelected(_currentRating);
          },
        ),
      );
    });
    return Row(
      children: [
        Expanded(
          child: Row(
            children: stars,
          ),
        ),
        Expanded(
          child: TextButton(
            onPressed: () {
              setState(() {
                _currentRating = 0;
              });

              this.widget.onRatingSelected(_currentRating);
            },
            child: Text(
              "Clear",
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return _buildBody();
  }

if you need more information please leave a comment.

This is how im calling the page

 Container(
                                width: 210,
                                height: 94,
                                //color: Colors.blue.withOpacity(0.5),
                                child: Column(
                                  children: [
                                    InkWell(
                                      onTap: () {
                                        setState(() {
                                          israting = true;
                                        });
                                        //  if( _rating !=null && _rating >0){
                                        // likevideo(videos.data()['id']);}
                                      },
                                      child: israting
                                          ? Container(
                                              height: 50,
                                              margin: EdgeInsets.fromLTRB(
                                                  0, 0, 5, 0),
                                              child: Column(
                                                children: [
                                                  Ratingpage((rating) {
                                                    setState(() {
                                                      _rating = rating;
                                                    });

                                                    if (_rating != null &&
                                                        _rating > 0) {
                                                      likevideo(
                                                          videos.data()['id'],
                                                          _rating);

                                                      print(delteuserchoicing);
                                                    } else if (_rating ==
                                                            null ||
                                                        _rating == 0) {
                                                      dislike(
                                                          videos.data()['id'],
                                                          _rating);
                                                    }
                                                  }),
                                                ],
                                              ),
                                            )
                                          : Icon(
                                              Icons.star,
                                              size: 37,
                                              color: videos
                                                      .data()['likes']
                                                      .contains(uid)
                                                  ? Colors.yellow
                                                  : Colors.white,
                                            ),
                                    ),

it is inside a column actually


Solution

  • So you have an issue of storing state between pages, then you have an issue of storing the rating upon app restart. 2 separate things. You may only be concerned with the former but here's how you would do both with GetX State management and GetStorage for local database storage. Same thing can be accomplished with literally any other state management solution ie. Provider, Riverpod, Bloc etc...

    GetStorage is interchangeable with SharedPreferences but I think anyone who has used both would agree GetStorage is a bit easier to use.

    To clean up my example I got rid of anything that wasn't necessary for accomplishing what you're asking. Depending on whats going on in the rest of your app, you probably won't need to bring back most or all of the variables I got rid of.

    For starters, let's move the logic and variables to a GetX class so they're accessible from anywhere in the app. It also helps clean up your UI code.

    class RatingController extends GetxController {
      int currentRating = 0;
      final box = GetStorage();
    
      @override
      void onInit() { // called whenever we initialize the controller
        super.onInit();
        currentRating = box.read('rating') ?? 0; // initializing current rating from storage or 0 if storage is null
      }
    
      void updateAndStoreRating(int rating) {
        currentRating = rating;
        box.write('rating', rating); // stores to local database
        update(); // triggers a rebuild of the GetBuilder Widget
      }
    
      Widget buildRatingStar(int index) {
        if (index < currentRating) {
          return Icon(
            Icons.star,
            color: Colors.yellow,
          );
        } else {
          return Icon(
            Icons.star,
            color: Colors.white,
          );
        }
      }
    }
    

    I added a button on this page just for demo purposes. Since this demo includes routing, I'm using Getx for a way easier to do routing also, but it's not at all related or necessary to answer your question. This page can now also be stateless.

    class Ratingpage extends StatelessWidget {
      static const id = 'rating_page'; // see GetMaterialApp for this usage
    
      final controller = Get.find<RatingController>(); // finding the same instance of initialized controller
    
      Widget _buildBody() {
        final stars = List<Widget>.generate(5, (index) {
          return GetBuilder<RatingController>( // rebuilds when update() is called from GetX class
            builder: (controller) => Expanded(
              child: GestureDetector(
                child: controller.buildRatingStar(index),
                onTap: () {
                  controller.updateAndStoreRating(index + 1); // +1 because index starts at 0 otherwise the star rating is offset by one
                },
              ),
            ),
          );
        });
        return Row(
          children: [
            Expanded(
              child: Row(
                children: stars,
              ),
            ),
            Expanded(
              child: TextButton(
                onPressed: () {
                  controller.updateAndStoreRating(0);
                },
                child: Text(
                  "Clear",
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ],
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            _buildBody(),
            ElevatedButton(
              onPressed: () {
                Get.to(() => OtherPage()); // equivalent of Navigator.push....
              },
              child: Text('Other Page'),
            )
          ],
        );
      }
    }
    

    Your main method now looks like this because we need to initialize the controller and storage.

    void main() async {
      await GetStorage.init();
      Get.put(RatingController());
    
      runApp(MyApp());
    }
    

    And again, only necessary for easier routing, we use GetMaterialApp and define pages there.

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return GetMaterialApp(
          title: 'Material App',
          home: Ratingpage(),
          getPages: [ // only necessary for routing, not for storage or state management
            GetPage(name: OtherPage.id, page: () => OtherPage()),
            GetPage(name: Ratingpage.id, page: () => Ratingpage()),
          ],
        );
      }
    }
    

    EDIT: Added with SharedPreferences due to an unmaintained package conflicting with GetStorage path provider dependency.

    Add SharedPreferences prefs; to your GetX class.

    This is your update function now.

    void updateAndStoreRating(int rating) {
        currentRating = rating;
        prefs.setInt('rating', rating); //SharedPreferences way
        update(); // triggers a rebuild of the GetBuilder Widget
      }
    

    Add an init function in GetX Controller class.

     Future<void> initSp() async {
        prefs = await SharedPreferences.getInstance();
        currentRating = prefs.getInt('rating') ?? 0;
      }
    

    Now your main is a bit different.

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      
      final controller = Get.put(RatingController());
      await controller.initSp();
    
      runApp(MyApp());
    }
    

    enter image description here