How to build in flutter [duplicate] - flutter

This question already has answers here:
How can I add a border to a widget in Flutter?
(18 answers)
Closed last year.
how to animate border around the square content.

This answer might be a little complicated for simple cases like this. Wish to have answer using paint. I am using Rive for this.
This rive file contains two states,
infinite loop
progress value 0-100
download and add on assets.
Check pub.dev to learn basic. To use this, we need StateMachineController
To lean basic, you can check rives-statemachine-with-textfiled and the complete project on GitHub
Rive Controller Widget on Gist
class RiveBorder extends StatefulWidget {
const RiveBorder({Key? key}) : super(key: key);
#override
_RiveBorderState createState() => _RiveBorderState();
}
class _RiveBorderState extends State<RiveBorder> {
StateMachineController? controller;
//progress value
SMIInput<double>? valueController;
// infinite loop
SMIInput<bool>? loopController;
Artboard? _riveArtboard;
_initRive() {
rootBundle.load("assets/new_file.riv").then((value) async {
final file = RiveFile.import(value);
final artboard = file.mainArtboard;
controller =
StateMachineController.fromArtboard(artboard, "State Machine 1");
if (controller != null) {
debugPrint("got state");
setState(() {
artboard.addController(controller!);
valueController = controller!.findInput('value');
loopController = controller!.findInput('loop');
// ignore: avoid_function_literals_in_foreach_calls
controller!.inputs.forEach((element) {
debugPrint(element.name);
});
});
}
_riveArtboard = artboard;
});
}
#override
void initState() {
_initRive();
super.initState();
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text("loop"),
const SizedBox(
width: 10,
),
Switch(
value: loopController == null ? false : loopController!.value,
onChanged: (value) {
setState(() {
if (loopController != null) loopController!.value = value;
});
},
),
],
),
Slider(
value: valueController == null ? 0 : valueController!.value,
min: 0,
max: 100,
onChanged: (value) {
setState(() {
valueController != null ? valueController!.value = value : 0;
});
},
),
SizedBox(
height: 100,
width: 100,
child: Stack(
alignment: Alignment.center,
children: [
_riveArtboard == null
? const CircularProgressIndicator()
: Rive(
artboard: _riveArtboard!,
),
const Icon(
Icons.umbrella,
size: 77,
)
],
),
)
],
),
),
);
}
}

Related

How to apply Scroll controller for multiple SilverList or ListView items?

I have found the internet a way to hide arrow when scrolling horizontally through FlutterLogo with CustomSrollView. The functionality works and i want to place it inside a ListView.builder or SilverList so i have multilple widgets with the scrollfunctionality but once i put CustomScrollView inside a list weither its ListView.builder or SilverList i get error:
ScrollController attached to multiple scroll views.
'package:flutter/src/widgets/scroll_controller.dart':
Failed assertion: line 108 pos 12: '_positions.length == 1'
This is the full code:
class AppView2 extends StatefulWidget {
const AppView2({
Key? key,
}) : super(key: key);
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView2> {
late ScrollController _hideButtonController;
var _isVisible;
#override
void initState() {
_isVisible = true;
_hideButtonController = ScrollController();
_hideButtonController.addListener(() {
if (_hideButtonController.position.userScrollDirection ==
ScrollDirection.reverse) {
if (_isVisible == true) {
/* only set when the previous state is false
* Less widget rebuilds
*/
print("**** $_isVisible up"); //Move IO away from setState
setState(() {
_isVisible = false;
});
}
} else {
if (_hideButtonController.position.userScrollDirection ==
ScrollDirection.forward) {
if (_isVisible == false) {
setState(() {
_isVisible = true;
});
}
}
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Example app bar"),
),
body: CustomScrollView(slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return SizedBox(
height: 500,
child: Stack(
children: [
CustomScrollView(
controller: _hideButtonController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverList(
delegate: SliverChildListDelegate(
<Widget>[
FlutterLogo(
size: 600,
)
],
),
),
),
],
),
Visibility(
visible: _isVisible,
child: Align(
alignment: Alignment.centerRight,
child: FloatingActionButton(
child: const Icon(Icons.arrow_forward_ios),
onPressed: () {}),
),
)
],
),
);
}, childCount: 10),
)
])));
}
}
How can i make sure that _hideButtonController works with multiple widget inside SilverList or ListView?
It is not very clear what you are trying to accomplish, but here is the reason why the error is thrown, and maybe some suggestions to help.
The error is thrown because you use the position getter of the ScrollController inside your listener code, while having multiple positions attached. Here is a quote from the documentation:
Calling this is only valid when only a single position is attached.
https://api.flutter.dev/flutter/widgets/ScrollController/position.html
You could use positions in your listener and check your conditions for any of the attached positions, although that is probably not what you want.
_hideButtonController.addListener(() {
if (_hideButtonController.positions.any((pos) => pos.userScrollDirection ==
ScrollDirection.reverse)) {
if (_isVisible == true) {
/* only set when the previous state is false
* Less widget rebuilds
*/
print("**** $_isVisible up"); //Move IO away from setState
setState(() {
_isVisible = false;
});
}
} else {
if (_hideButtonController.positions.any((pos) => pos.userScrollDirection ==
ScrollDirection.forward)) {
if (_isVisible == false) {
setState(() {
_isVisible = true;
});
}
}
}
});
If you only want to share the code for hiding the button when a condition is met for a singular horizontal scroller, your best bet is probably to write your own widget which holds its own ScrollController with the same listener code you already have. This allows every child of the vertical list to have its own ScrollController for the horizontal list and thus allows you to only hide the button for the affected controller:
class LogoHidingScrollView extends StatefulWidget {
const LogoHidingScrollView({
Key? key,
}) : super(key: key);
_LogoHidingScrollViewState createState() => _LogoHidingScrollViewState();
}
class _LogoHidingScrollViewState extends State<LogoHidingScrollView> {
final ScrollController _scrollController = ScrollController();
bool _isVisible = true;
void initState() {
_isVisible = true;
_scrollController.addListener(() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
if (_isVisible == true) {
/* only set when the previous state is false
* Less widget rebuilds
*/
print("**** $_isVisible up"); //Move IO away from setState
setState(() {
_isVisible = false;
});
}
} else {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
if (_isVisible == false) {
setState(() {
_isVisible = true;
});
}
}
}
});
super.initState();
}
Widget build(BuildContext context) {
return SizedBox(
height: 500,
child: Stack(
children: [
CustomScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverList(
delegate: SliverChildListDelegate(
<Widget>[
FlutterLogo(
size: 600,
)
],
),
),
),
],
),
Visibility(
visible: _isVisible,
child: Align(
alignment: Alignment.centerRight,
child: FloatingActionButton(
child: const Icon(Icons.arrow_forward_ios), onPressed: () {}),
),
)
],
),
);
}
}
If you truly want to synchronize the scrolling behaviour between all those horizontal scroll views you could have a look at: https://pub.dev/packages/linked_scroll_controller

flutter - futurebuilder doesn't load until textformfield is clicked

My Bad
It was problem with my future function HashtagService().getSuggestion('topic'); returning empty List before data is properly loaded. EditTopicPage didn't have any problem.
Original Question
I have text form field inside futurebuilder. When I first open page, future is not loaded. When I click on text field to enter something, future is loaded.
I want future to be loaded when the page is first opened.
class EditTopicPage extends StatefulWidget {
const EditTopicPage({required UserProfileModel userProfile, Key? key}) : _userProfile = userProfile, super(key: key);
final UserProfileModel _userProfile;
#override
_EditTopicPageState createState() => _EditTopicPageState();
}
class _EditTopicPageState extends State<EditTopicPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();
List<String> _hashList = [];
List<String> _suggestionList = [];
late final Future<List<String>> _future;
bool _disabled = true;
final RegExp hashRegex = RegExp(r'^[a-z|A-Z|ㄱ-ㅎ|ㅏ-ㅣ|가-힣|ㆍ|ᆢ]*$');
#override
void initState() {
_future = HashtagService().getSuggestion('topic');
_hashList = widget._userProfile.topic.cast<String>();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
UserProfileService _userProfileService = UserProfileService();
final Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
appBar: TopBar(pageTitle: "관심 대화 주제 수정", context: context),
body: ListView(
padding: EdgeInsets.all(16.sp),
children: [
FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
_suggestionList = List<String>.from(snapshot.data.reversed);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleLineTextFormField(
controller: _controller,
hintText: '어떤 주제로 대화하고 싶으신가요?',
onChanged: (value) {
if (value.length > 1) {
var lastChar = value.substring(
value.length - 1);
if (!hashRegex.hasMatch(lastChar)) {
var newHash = value.substring(
0, value.length - 1);
if (!_hashList.contains(newHash)) {
setState(() {
_hashList.add(newHash);
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
}
_controller.clear();
}
} // else if (_expHash.length == 3 && value.isNotEmpty) {
_formKey.currentState!.validate();
},
validator: (value) {
if (!hashRegex.hasMatch(value!)) {
return '\u26a0 한글, 영문만 가능해요';
}
return null;
}
),
DefaultSpacing(),
Row(
children: [
Text('추천 : ',
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey[800],
),
),
Wrap(
spacing: 6.0,
runSpacing: 6.0,
children: _suggestionList.map((suggestion) =>
HashtagSuggestionChip(
suggestion: suggestion,
type: 'topic',
onPressed: () {
if (!_hashList.contains(suggestion)) {
setState(() {
_hashList.add(suggestion);
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
}
},
)).toList(),
),
],
),
DefaultSpacing(),
Wrap(
spacing: 6.0,
runSpacing: 6.0,
children: _hashList.map((hashtag) =>
HashtagChip(
hashtag: hashtag,
type: 'topic',
onDelete: () {
setState(() {
_hashList.remove(hashtag);
_formKey.currentState!.validate();
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
},
)).toList()
),
]),
);
}
return CircularProgressIndicator();
}
),
],
),
floatingActionButton: FloatingSaveButton(context: context,
text: '저장',
width: size.width * 0.9,
disabled: _disabled,
onPressed: () async {
_userProfileService.updateTopicHashtag(hashList: _hashList);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(SaveSnackBar());
}),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
Result: when the page is first opened
Neither hashtag nor progress indicator is shown.
After text form field is selected
I searched similar questions in stackoverflow, but none of the answers solved my problem.
Try like this
#override
void initState() {
setState(() {
_future = HashtagService().getSuggestion('topic');
_hashList = widget._userProfile.topic.cast<String>();
});
super.initState();
}
so your _future is async so the widget will be loaded before loading the service

Rive's stateMachine with TextFiled

I Like to create animation that will follow the TextField text, here is video from
Flutter YouTube.
Now how can I follow the Target.
Here is My rive file on rive.app or GitHub and Design.
We need a StateMachineController and SMIInput<double> that will be responsible to follow the text.
Result
OutPutVideo
Result with Slider
You can follow the GitHub Repository or
class TextFieldWithRive extends StatefulWidget {
TextFieldWithRive({Key? key}) : super(key: key);
#override
_TextFieldWithRiveState createState() => _TextFieldWithRiveState();
}
class _TextFieldWithRiveState extends State<TextFieldWithRive> {
StateMachineController? controller;
SMIInput<double>? valueController;
Artboard? _riveArtboard;
double sliderVal = 0.0;
/// change value based on size>Width
final double strengthOverTextFiled = 1.5;
final TextEditingController textEditingController = TextEditingController();
#override
void initState() {
super.initState();
rootBundle.load("rives/eyeMovement.riv").then((value) async {
final file = RiveFile.import(value);
final artboard = file.mainArtboard;
controller = StateMachineController.fromArtboard(artboard, "eyeMovement");
if (controller != null) {
print("got state");
setState(() {
artboard.addController(controller!);
valueController = controller!.findInput('moevement_controll');
controller!.inputs.forEach((element) {
print(element.name);
});
});
}
_riveArtboard = artboard;
});
///* eye controll with textFiled
textEditingController.addListener(() {
print(textEditingController.text);
if (valueController != null) {
valueController!.value =
textEditingController.text.length * strengthOverTextFiled;
}
});
}
#override
void dispose() {
textEditingController.removeListener(() {});
textEditingController.dispose();
controller!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LayoutBuilder(
builder: (context, constraints) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: constraints.maxWidth * .5,
width: constraints.maxWidth * .5,
child: _riveArtboard == null
? CircularProgressIndicator()
: Rive(
artboard: _riveArtboard!,
),
),
SizedBox(
width: constraints.maxWidth * .8,
child: TextField(
controller: textEditingController,
decoration: InputDecoration(hintText: "keep typing"),
),
),
],
),
),
),
);
}
}

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
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());
}

ChipSelect callback is not updating the UI. Flutter

I gave up trying to find the reason setState() is not running build method. I have a ChoiceChip which has a callback function. Debug showed me that I actually receive the selected chip at the callback but setState() is not updating the ui. I have spent all day trying to understand why setState() is not running the build() method. Here is my code
class SocialStoryCategory extends StatefulWidget {
final Function(String) onMenuItemPress;
SocialStoryCategory({Key key, #required this.onMenuItemPress}) : sup er(key: key);
#override
_SocialStoryCategoryState createState() => _SocialStoryCategoryState();
}
class _SocialStoryCategoryState extends State<SocialStoryCategory> {
int _value = 0;
List<String> categoryList;
#override
Widget build(BuildContext context) {
categoryList = [
NoomeeLocalizations.of(context).trans('All'),
NoomeeLocalizations.of(context).trans('Communication'),
NoomeeLocalizations.of(context).trans('Behavioral'),
NoomeeLocalizations.of(context).trans('ADL'),
NoomeeLocalizations.of(context).trans('Other')
];
return Wrap(
spacing: 4,
children: List<Widget>.generate(5, (int index) {
return Theme(
data: ThemeData.from(
colorScheme: ColorScheme.light(primary: Colors.white)),
child: Container(
child: ChoiceChip(
elevation: 3,
selectedColor: Colors.lightBlueAccent,
label: Text(categoryList.elementAt(index)),
selected: _value == index,
onSelected: (bool selected) {
setState(() {
_value = selected ? index : null;
if (categoryList.elementAt(_value) == "All") {
widget.onMenuItemPress("");
} else {
widget.onMenuItemPress(categoryList.elementAt(_value));
}
});
},
),
),
);
}).toList());
}
}
Here is the place where I get the callback
class SocialStoriesHome extends StatefulWidget {
#override
_SocialStoriesHomeState createState() => _SocialStoriesHomeState();
}
class _SocialStoriesHomeState extends State<SocialStoriesHome>
with TickerProviderStateMixin {
String title;
TabController _tabController;
int _activeTabIndex = 0;
String _defaultStoryCategory;
_goToDetailsPage() {
Navigator.of(context).pushNamed("parent/storyDetails");
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 2);
_defaultStoryCategory = '';
}
#override
Widget build(BuildContext context) {
return BaseWidget<SocialStoryViewModel>(
model: new SocialStoryViewModel(
socialStoriesService: Provider.of(context),
),
onModelReady: (model) =>
model.fetchDefaultStoriesByCategory(_defaultStoryCategory),
builder: (context, model, child) => DefaultTabController(
length: 2,
child: Scaffold(
body: model.busy
? Center(child: CircularProgressIndicator())
: Container(
child: Column(
children: <Widget>[
new SocialStoryCategory(
onMenuItemPress: (selection) {
setState(() {
_defaultStoryCategory = selection;
});
},
),
Expanded(
child: ListView(
children: getStories(model.socialStories),
),
),
],
),
),
);
}
}
List<Widget> getStories(List<SocialStoryModel> storyList) {
List<Widget> list = List<Widget>();
for (int i = 0; i < storyList.length; i++) {
list.add(Padding(
padding: const EdgeInsets.all(8.0),
child: Template(
title: storyList[i].name,
subTitle: storyList[i].categories,
hadEditIcon: false),
));
}
return list;
}
Finally I have found the solution.
I have simply replaced
new SocialStoryCategory(onMenuItemPress: (selection) {
setState(() {
_defaultStoryCategory = selection;
});
},
),
to
new SocialStoryCategory(
onMenuItemPress: (selection) {
model.fetchDefaultStoriesByCategory(selection);
},
),
my viewModel extend change notifier and build a child as consumer so I totally understand why it works. But I still do not understand why the previous version was not working. I will feel happy again only when you explain me the problem,