I want to make an app which connects to server and sends it different requests, gets responses etc. For that I have a Client class (not a widget), which handles all the network stuff. I use bloc pattern to marshal requests to Client and get back responses. This Client instance should be accessible all over the widget tree, so I use InheritedWidget. My idea is that the first thing user sees after opening an app is splash screen, which should change to some other page when connection to server is established. I subscribe to response stream in SplashScreen's build method and navigate to different page when response is received, but for some reason accessing InheritedWidget triggers rebuild of SplashScreen which leads to accessing InheritedWidget which leads to rebuild and so on and so forth. And obviously stream starts complaining that I already subscribed to it. I do not change InheritedWidget anywhere, and updateShouldNotify is not called, and I set it to return false anyway. Here is minimal reproducible example. It obviously does not perform any real network communication, but it demonstrates what I try to say.
var client = ClientInheritedData.of(context).client leads to rebuild. If I comment it and lines that use client, rebuild is not triggered.
class Client {
StreamController<int> _eventStreamController;
StreamSink<int> get eventSink => _eventStreamController.sink;
Stream<int> _eventStream;
StreamController<String> _responseStreamController;
StreamSink<String> _responseSink;
Stream<String> get responseStream => _responseStreamController.stream;
Client() {
_eventStreamController = StreamController();
_eventStream = _eventStreamController.stream;
_responseStreamController = StreamController<String>();
_responseSink = _responseStreamController.sink;
_eventStream.listen((event) async {
if (event == 1) {
await Future.delayed(Duration(seconds: 2)); // simulate some work
_responseSink.add('Connected!');
}
});
}
}
void main() => runApp(ClientInheritedData(Client(), MyApp()));
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.green,
),
initialRoute: '/',
onGenerateRoute: RouteGenerator.generate,
);
}
}
class SplashScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var client = ClientInheritedData.of(context).client;
client.eventSink.add(1);
client.responseStream.listen((response) {
Navigator.of(context).pushNamed('/Sign Up');
});
return Scaffold(
appBar: AppBar(
title: Text('Splash Screen'),
),
body: Center(
child: Text('Greetings!'),
),
);
}
}
class SignUp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sign Up'),
),
body: Center(
child: Text('Hey man'),
),
);
}
}
class ClientInheritedData extends InheritedWidget {
final Client client;
const ClientInheritedData(this.client, Widget child) : super(child: child);
static ClientInheritedData of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ClientInheritedData) as ClientInheritedData;
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
}
class RouteGenerator {
static Route<dynamic> generate(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => SplashScreen(),
);
case '/Sign Up':
return MaterialPageRoute(
builder: (context) => SignUp(),
);
}
}
}