Flutter Hooks Riverpod not updating widget despite provider being refreshed - flutter

I've been studying flutter for a couple of months and I am now experimenting with Hooks and Riverpod which would be very important so some results can be cached by the provider and reused and only really re-fetched when there's an update.
But I hit a point here with an issue where I can't wrap my head around the provider update to reflect in the Widget. Full example can be checked out from here -> https://github.com/codespair/riverpod_update_issue I've added some debug printing and I can see the provider is properly refreshed but the changes don't reflect on the widget.
The example has a working sample provider:
// create simple FutureProvider with respective future call next
final futureListProvider =
FutureProvider.family<List<String>, int>((ref, value) => _getList(value));
// in a real case there would be an await call inside this function to network or local db or file system, etc...
Future<List<String>> _getList(int value) async {
List<String> result = [...validValues];
if (value == -1) {
// do nothing just return original result...
} else {
result = []..add(result[value]);
}
debugPrint('Provider refreshed, result => $result');
return result;
}
a drop down list when changed refreshes the provider:
Container(
alignment: Alignment.center,
padding: EdgeInsets.fromLTRB(5, 2, 5, 1),
child: DropdownButton<String>(
key: UniqueKey(),
value: dropDownValue.value.toString(),
icon: Icon(Icons.arrow_drop_down),
iconSize: 24,
elevation: 16,
underline: Container(
height: 1,
color: Theme.of(context).primaryColor,
),
onChanged: (String? newValue) {
dropDownValue.value = newValue!;
context
.refresh(futureListProvider(intFromString(newValue)));
},
items: validValues
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: Theme.of(context).primaryTextTheme.subtitle1,
),
);
}).toList(),
),
),
And a simple list which uses the provider elements to render which despite the provider being properly refreshed as you can see in the debugPrinting it never updates:
Container(
key: UniqueKey(),
height: 200,
child: stringListProvider.when(
data: (stringList) {
debugPrint('List from Provider.when $stringList');
return MyListWidget(stringList);
// return _buildList(stringList);
},
loading: () => CircularProgressIndicator(),
error: (_, __) => Text('OOOPsss error'),
),
),
]),
class MyListWidget extends HookWidget {
final GlobalKey<ScaffoldState> _widgetKey = GlobalKey<ScaffoldState>();
final List<String> stringList;
MyListWidget(this.stringList);
#override
Widget build(BuildContext context) {
debugPrint('stringList in MyListWidget.build $stringList');
return ListView.builder(
key: _widgetKey,
itemCount: stringList.length,
itemBuilder: (BuildContext context, int index) {
return Card(
key: UniqueKey(),
child: Padding(
padding: EdgeInsets.all(10), child: Text(stringList[index])),
);
},
);
}
As I am evaluating approaches to develop some applications I am getting inclined to adopt a more straightforward approach to handle such cases so I am also open to evaluate simpler, more straightforward approaches but I really like some of the features like the useMemoized, useState from hooks_riverpod.

One thing I wanted to note before we get started is you can still use useMemoized, useState, etc. without hooks_riverpod, with flutter_hooks.
As far as your problem, you are misusing family. When you pass a new value into family, you are actually creating another provider. That's why your list prints correctly, because it is, but the correct result is stuck in a ProviderFamily you aren't reading.
The simpler approach is to create a StateProvider that you use to store the currently selected value and watch that provider from your FutureProvider. It will update the list automatically without having to refresh.
final selectedItemProvider = StateProvider<int>((_) => -1);
final futureListProvider = FutureProvider<List<String>>((ref) async {
final selected = ref.watch(selectedItemProvider).state;
return _getList(selected);
});
DropdownButton<String>(
...
onChanged: (String? newValue) {
dropDownValue.value = newValue!;
context.read(selectedItemProvider).state = intFromString(newValue);
},
}

Related

DropdownButton not working as expected in Flutter

Am new to Flutter Development. Am populating a snapshot data received from API in DropdownButtonlist. everything works fine. but when I change the items in the list I get above error. am not sure which one is causing the pbm. i surfed the net a lot but could not find the solution. I get the error even if I have one item in the list. I get error "Error: Either zero or 2 or more [DropdownMenuItem]s were detected with the same value in flutter "
Thanks in advance
FutureBuilder(
future:Api.getSchemes(context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Color(0xffF3D876),
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton<ClsSchemes>(
alignment: AlignmentDirectional.centerStart,
isExpanded: true,
value: dropDownValue,
hint: Text(dropDownValue.Scheme_Name ?? 'Make a selection'),
items: snapshot.data.map<DropdownMenuItem<ClsSchemes>>((item) {
return DropdownMenuItem<ClsSchemes>(
value: item,
child: Text(item.Scheme_Name),
);
}).toList(),
onChanged: (value) {
setState(() {
dropDownValue = value!;
TotalDues = value.Total_Dues;
});
},
),
)
: Container(
child: Center(
child: Text('Loading...'),
),
);
},
),
Since you are using an instance of ClsSchemes as your value, you need to make sure that operator == actually works properly for your class.
For this you need to override the == operator and the hashCode:
Example take from here
class Example {
final int value;
Example(this.value);
#override
bool operator ==(Object other) =>
other is Example &&
other.runtimeType == runtimeType &&
other.value == value;
#override
int get hashCode => value.hashCode;
}
You need to find out what the identifying value of your class is. When are two of those classes equal?
If you already have specific fields that are your "identity" fields, the package equatable makes it a little easier.

Expected a value of type 'String', but got one of type 'List<dynamic>' for my DropdownMenu

Got an API call that returns a bunch of data for my app. This particular data set is a Map<String, List<dynamic>>, I'm processing this data to make it usable within my app and passing it around to necessary widgets. I came across his error which makes no sense to me but it is self-explanatory looking at the code I cant see anything.
This code is a part of a bigger code please comment if you want me to add it as it just takes in a few arguments to process the Future and create the Map<String, List<dynamic>>.
This is the code where the error is being thrown (Line:45)
#override
Widget build(BuildContext context) {
return FutureBuilder<Map<String, List<dynamic>>>(
future: options,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data!.values.length,
itemBuilder: ((context, index) {
return DropdownMenu(items: snapshot.data!.values.toList()); //Line: 45
}),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return const CircularProgressIndicator();
}
},
);
}
This is my DropdownMenu Class
class DropdownMenu extends StatefulWidget {
DropdownMenu({super.key, required this.items});
List<dynamic> items;
#override
State<DropdownMenu> createState() => _DropdownMenuState(items);
}
class _DropdownMenuState extends State<DropdownMenu> {
_DropdownMenuState(this.items);
String? value;
List<dynamic> items;
#override
void initState() {
super.initState();
widget.items = items;
}
#override
Widget build(BuildContext context) {
return Container(
width: 300,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black, width: 2)),
child: DropdownButtonHideUnderline(
child: DropdownButton<dynamic>(
value: value,
onChanged: (value) => setState(() => this.value = value),
items: items.map(buildMenuItem).toList(),
),
),
);
}
DropdownMenuItem<dynamic> buildMenuItem(dynamic item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
);
}
Error
The following TypeErrorImpl was thrown building DropdownMenu(dirty, state:
_DropdownMenuState#69c5b):
Expected a value of type 'String', but got one of type 'List<dynamic>'
The relevant error-causing widget was:
DropdownMenu
DropdownMenu:file:///C:/Main%20Storage/_GitHub%20Repos/flutter_fontend_client/lib/components/options__dropdown_menu.dart:45:22
After some debugging... I added this piece of code
var result1 = {
for (var value in snapshot.data!.values.toList())
value.first: value
};
print("Values of the snapshot: $result1");
The result is a big awkward and IDK why it like this. It prints out a json style format string {'key': ['keyStrings', 'keyStrings']
Got a different answer from someone in NorDev Discord.
I will show the answer here + keep the accepted answer as both work and I think that people will appreciate that there is multiple ways of solving this.
return DropdownMenu(items: snapshot.data!.values.elementAt(index));
According to your code, your response is a Map with strings as keys and List as values. That means that snapshot.data!.values.toList() is a list with (possibly) multiple List<dynamic> that you are passing to DropdownMenu.
DropdownMenu expects that the elements of the list are of type String but they are not.
I suspect what you want to do is actually get the first list, so you could do
return DropdownMenu(items: snapshot.data!.values.first);

Flutter FutureBuilder Snapshot is null but Future Does return data

While working with Flutter for a new application client for Kanboard, I encountered the following problem. I have a FutureBuilder that should return a select dropdown menu with items but, for some reason, the Snapshot data is null, although the Future method does resolves and has data on return.
Full page.dart code here: https://pastebin.com/J48nxsdZ
The block having the problem is the following:
Widget _columnSelect() {
return FutureBuilder(
future: columnProvider.getColumns(task.projectId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> columnList = [];
if (snapshot.hasData) {
columnList.add(DropdownMenuItem<String>(
child: Text('Select Column'), value: 0.toString()));
_columns = snapshot.data;
} else {
columnList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_columns.forEach((column) {
columnList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
column.title,
),
),
value: column.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.view_column, color: Colors.blue),
),
items: columnList,
value: _columnId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_columnId = newValue;
},
),
);
},
);
}
This is a duplicate of a widget in the same form for a user dropdown select. The original widget (in the same page) is the following:
Widget _ownerSelect() {
return FutureBuilder(
future: userProvider.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> usernameList = [];
if (snapshot.hasData) {
usernameList.add(DropdownMenuItem<String>(
child: Text('Select Owner'), value: 0.toString()));
_users = snapshot.data;
} else {
usernameList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_users.forEach((user) {
usernameList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
user.name,
),
),
value: user.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.person, color: Colors.blue),
),
items: usernameList,
value: _ownerId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_ownerId = newValue;
},
),
);
},
);
}
For some reason, the "_columnSelect" AsyncSnapshot is null always, even when the Future method is working fine:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kanboard/src/models/column_model.dart';
import 'package:kanboard/src/preferences/user_preferences.dart';
class ColumnProvider {
final _prefs = new UserPreferences();
Future<List<ColumnModel>> getColumns(projectId) async {
final Map<String, dynamic> parameters = {
"jsonrpc": "2.0",
"method": "getColumns",
"id": 887036325,
"params": {"project_id": projectId}
};
final credentials = "${_prefs.username}:${_prefs.password}";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials);
final resp = await http.post(
Uri.parse(_prefs.endpoint),
headers: <String, String>{"Authorization": "Basic $encoded"},
body: json.encode(parameters),
);
final decodedData = json.decode(utf8.decode(resp.bodyBytes));
final List<ColumnModel> columns = [];
final List<dynamic> results = decodedData['result'];
if (decodedData == null) return [];
results.forEach((column) {
final columnTemp = ColumnModel.fromJson(column);
columns.add(columnTemp);
});
print(columns);
return columns;
}
}
The output of "print(columns)" returns:
I/flutter ( 9486): [Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel']
I don't know what I'm missing here. The form has 2 users dropdown select (with the original FutureBuilder Widget) which works just fine. The Column widget with the Future Builder is the one with the "null" problem in snapshot.data.
Thank you in advance for your time and support with this!
I just found where the problem was:
In the form page(new Task page), The columnProvider.getColumns(task.projectId)) wasn't executing because the "task.projectId" parameter is a String, but the API needs an int.
I was confused because the method were being called by the previous page (A project Page with all the tasks) and the getColumn's argument was indeed an integer: int.parse(projectId).
The Kanboard API doesn't return an error code if the ID parameter is other than INT with this specific call "getColumns" (for some reason).
Of course, Flutter (or Dart) is waiting for a response from http.post that would never arrive. When comparing the two calls from the two pages, I noticed the difference.
So, in conclusion, I specified the int data type argument in the getColumn definition in order to avoid any confusion:
Future<List<ColumnModel>> getColumns(int projectId) async {
Best Regards!

How to open DropdownButton when other widget is tapped, in Flutter?

I need to have a DropdownButton's list of options open/show programmatically when some other widget is tapped. I know that this may not be UI-best-practice and all, but I need this behavior:
As an example, in a structure like the one below, I may need to have taping Text("every") to open the neighboring DropdownButton's dropdown list, behaviors similar to clicking a <select>'s label in HTML.
Row(children: [
Padding(
padding: const EdgeInsets.only(right: 16),
child: Text('every'),
),
Expanded(
child: DropdownButton<String>(
value: _data['every'],
onChanged: (String val) => setState(() => _data['every'] = val),
items: _every_options.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
},
).toList(),
isExpanded: true,
),
),
]);
NOTE: I am in need though of the general solution to this problem, not just how to make that Text behave somewhat "like a HTML label" in the tree below. It may need to be triggered to open by maybe a further away button etc.
The other answer is the best way to do this, but as requested by the OP in comments, here are two very "hacky" ways to achieve this, yet without implementing custom widgets.
1. Access DropdownButton widget tree directly using GlobalKey
If we look at the source code of DropdownButton, we can notice that it uses GestureDetector to handle taps. However, it's not a direct descendant of DropdownButton, and we cannot depend on tree structure of other widgets, so the only reasonably stable way to find the detector is to do the search recursively.
One example is worth a thousand explanations:
class DemoDropdown extends StatefulWidget {
#override
InputDropdownState createState() => DemoDropdownState();
}
class DemoDropdownState<T> extends State<DemoDropdown> {
/// This is the global key, which will be used to traverse [DropdownButton]s widget tree
GlobalKey _dropdownButtonKey;
void openDropdown() {
GestureDetector detector;
void searchForGestureDetector(BuildContext element) {
element.visitChildElements((element) {
if (element.widget != null && element.widget is GestureDetector) {
detector = element.widget;
return false;
} else {
searchForGestureDetector(element);
}
return true;
});
}
searchForGestureDetector(_dropdownButtonKey.currentContext);
assert(detector != null);
detector.onTap();
}
#override
Widget build(BuildContext context) {
final dropdown = DropdownButton<int>(
key: _dropdownButtonKey,
items: [
DropdownMenuItem(value: 1, child: Text('1')),
DropdownMenuItem(value: 2, child: Text('2')),
DropdownMenuItem(value: 3, child: Text('3')),
],
onChanged: (int value) {},
);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Offstage(child: dropdown),
FlatButton(onPressed: openDropdown, child: Text('CLICK ME')),
],
);
}
}
2. Use Actions.invoke
One of the recent features of Flutter is Actions (I'm not sure what it's meant for, I've only noticed it today after flutter upgrade), and DropdownButton uses it for reacting to different... well, actions.
So a little tiny bit less hacky way to trigger the button would be to find the context of Actions widget and invoke the necessary action.
There are two advantages of this approach: firstly, Actions widget is a bit higher in the tree, so traversing that tree wouldn't be as long as with GestureDetector, and secondly, Actions seems to be a more generic mechanism than gesture detection, so it's less likely to disappear from DropdownButton in the future.
// The rest of the code is the same
void openDropdown() {
_dropdownButtonKey.currentContext.visitChildElements((element) {
if (element.widget != null && element.widget is Semantics) {
element.visitChildElements((element) {
if (element.widget != null && element.widget is Actions) {
element.visitChildElements((element) {
Actions.invoke(element, Intent(ActivateAction.key));
return false;
});
}
});
}
});
}
It's one (of many) designed API limitations...
The easiest approach to accomplish what you want, without modifying the SDK, copy dropdown.dart, and create your own version of it, let's say custom_dropdown.dart, and paste the code there ...
in line 546, rename the class to CustomDropdownButton, and in line 660 and 663 rename _DropdownButtonState to CustomDropdownButtonState, ( we need the state class to be exposed outside the file ).
Now you can do whatever you want with it,
although you were interested in the _handleTap(), to open the overlay menu options.
Instead of making _handleTap() public, and refactor the code, add another method like:
(line 726)
void callTap() => _handleTap();
Now, change your code to use your DropdownButton instead of the Flutter's DropdownButton, the key is to "set the key" (Global one) :P
// some stateful widget implementation.
Map<String, String> _data;
List<String> _every_options;
// we need the globalKey to access the State.
final GlobalKey dropdownKey = GlobalKey();
#override
void initState() {
_every_options = List.generate(10, (i) => "item $i");
_data = {'every': _every_options.first};
simulateClick();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Row(children: [
Padding(
padding: const EdgeInsets.only(right: 16),
child: Text('every'),
),
Expanded(
child: CustomDropdownButton<String>(
key: dropdownKey,
value: _data['every'],
onChanged: (String val) => setState(() => _data['every'] = val),
items: _every_options
.map((str) => DropdownMenuItem(
value: str,
child: Text(str),
))
.toList(),
isExpanded: true,
),
),
]),
);
}
void simulateClick() {
Timer(Duration(seconds: 2), () {
// here's the "magic" to retrieve the state... not very elegant, but works.
CustomDropdownButtonState state = dropdownKey.currentState;
state.callTap();
});
}

Check / Uncheck ListTiles in ListView.builder() - Flutter

I have a ListView.builder(); in showModalBottomSheet();
Need to select / deselect multiple items on tap everything is well but need to close the modal and show it again to apply changes, another thing is the ListTiles sometimes duplicated more than once, function emptyList doesn't work well.
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'book_details.dart' show BookDetails;
class Explore extends StatefulWidget {
#override
_ExploreState createState() => _ExploreState();
}
var _books,
_categories,
_arranges,
_currentCategory,
_selected,
_primeColor,
_currentFilter,
_isThereIsFilters,
_booksContainer,
_booksWithFilters,
_isLoading,
_noBooks,
_itemIcon;
final GlobalKey<ScaffoldState> _scaffoldKeyExplore =
new GlobalKey<ScaffoldState>();
List<String> _getCats = new List();
List<String> _getArrs = new List();
void _insertCategories() {
for (int i = 0; i < _categories.length; i++) {
_getCats.add(_categories[i]);
}
_getCats.sort();
}
void _insertArranges() {
for (int i = 0; i < _arranges.length; i++) {
_getArrs.add(_arranges[i]);
}
}
class _ExploreState extends State<Explore> with TickerProviderStateMixin {
onCatChange(String category) {
setState(() {
_currentCategory = category;
});
}
#override
void initState() {
super.initState();
_primeColor = Color.fromRGBO(239, 89, 39, 1.0);
_categories = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_arranges = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_currentFilter = _arranges[0];
_selected = [];
_isThereIsFilters = false;
}
void emptyList(List list) {
for (var i = 0; i < list.length; i++) {
list.remove(list[i]);
}
}
_showSheet(String type) {
switch (type) {
case "filters":
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Expanded(
child: new ListView.builder(
itemCount: _getArrs[0] != null ? _getArrs.length : 0,
itemBuilder: (BuildContext context, int i) {
return new RadioListTile(
title: Text(_getArrs[i]),
value: _getArrs[i],
groupValue: _currentFilter,
onChanged: (val) {
setState(() {
_currentFilter = val;
});
});
}),
)
])),
);
});
break;
case "categories":
default:
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Container(
color: _primeColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () {
emptyList(_selected);
//Navigator.pop(context);
//_showSheet(type);
}),
IconButton(
icon:
Icon(Icons.done_all, color: Colors.white),
onPressed: () {
if (_selected.length > 0) {
_getFilteredBooks(_selected);
setState(() {
_isThereIsFilters = true;
});
} else {
setState(() {
_isThereIsFilters = false;
});
}
Navigator.pop(context);
})
]),
),
Expanded(
child: new ListView.builder(
itemCount: _getCats != null ? _getCats.length : 0,
itemBuilder: (BuildContext context, int i) {
final _isSelected = _selected.contains(_getCats[i]);
return new ListTile(
leading: Icon(Icons.category),
trailing: _isSelected ? Icon(Icons.done) : null,
title: Text(_getCats[i]),
onTap: () {
setState(() {
_isSelected
? _selected.remove(_getCats[i])
: _selected.add(_getCats[i]);
});
//Navigator.pop(context);
//_showSheet(type);
});
}),
)
])),
);
});
break;
}
}
#override
Widget build(BuildContext context) {
return new Directionality(
textDirection: TextDirection.rtl,
child: new Scaffold(
key: _scaffoldKeyExplore,
appBar:
AppBar(title: Text("استكشاف"), elevation: 0.0, actions: <Widget>[
IconButton(
icon: Icon(Icons.category, color: _primeColor),
onPressed: () => _showSheet("categories")),
IconButton(
icon: Icon(Icons.filter_list, color: _primeColor),
onPressed: () => _showSheet("filters"))
]),
body: Center(child: Text("Nothing..."));
));
}
}
Thank you
need to close the modal and show it again to apply changes
This happens because the showModalBottomSheet's builder needs to be called again to reflect the changes.
In Flutter, StatefulWidgets should be able to rebuild any time the state changes - which is not the case here, because of the bottom sheet being shown.
Why did I run into this issue (on a meta level)?
Storing the state in StatefulWidgets is useful for saving UI state, but you quickly outgrow this technique if you want to store some "app state" or "data state" that is independent of the screen it's on.
It is finally time to fundamentally rethink your state management and settle on a full-fledged state management pattern that decouples the state from the widgets. Luckily, there are a few to choose from:
Making everything global, like you did above. This is generally not a good idea, as you break the contract of setState (state can be modified without the widgets being notified). Also, you break hot restart and stuff like that.
Using an InheritedWidget, where widgets below a root widget can access the same state.
Using a ScopedModel, which builds on top of that.
Using the infamous BLoC pattern, which also builds on top of the InheritedWidget, but adds some Stream-y stuff to make everything more reactive.
Probably many more.
Here is a great Youtube video about state management from Google I/O, where several patterns are being presented.
Anyways, are bottom sheets the right widget for the task ahead?
According to the Material Design spec, the modal bottom sheet is "an alternative to inline menus or simple dialogs on mobile, providing room for additional items, longer descriptions, and iconography".
More concrete, the showModalBottomSheet function is designed to show a widget that doesn't affect the parent over time, but rather - if at all - at a single point in time. That's why it returns a Future<T>, not a Stream<T>.
Be aware that you are trying to use the bottom sheet in a way that it's not intended to be used.
In your case, I'd recommend just using a new screen.