Flutter websockets working in local web but not in Firebase Hosting - firebase

I'm doing some tests with web_socket_channel Flutter plugin and I've noticed a very strange behavior. I've implemented flutter-dev's example, just changing the socket kind to HtmlWebSocketChannel in order to make it work in web builds.
If I compile my app with flutter build web --release and later I expose it with a local webserver, it works perfectly fine. Same happens if I execute it in debug mode.
However, if I deploy the release version to Firebase hosting (firebase deploy), the widgets where a HtmlWebSocketChannel is present are rendered as a grey box. If I remove those instances, all widgets are rendered as usual.
I thought Firebase hosting was nothing more that a very simple web server, I can't see how can it interfere with specific widgets in a Flutter app. Maybe the cause is related to the fact I'm accesing a remote URL?
Any help will be appreciated!
Here's the code of the app I'm deploying:
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/html.dart';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'WebSocket Demo';
return MaterialApp(
title: title,
home: MyHomePage(
title: title,
channel: HtmlWebSocketChannel.connect('ws://echo.websocket.org'),
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
final WebSocketChannel channel;
MyHomePage({Key key, #required this.title, #required this.channel})
: super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
child: TextFormField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
),
),
StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
);
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: Icon(Icons.send),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
widget.channel.sink.add(_controller.text);
}
}
#override
void dispose() {
widget.channel.sink.close();
super.dispose();
}
}

As mentioned above in my comments this seems to be an issue with trying to access insecure resource from a secure environment as https. Here is a working demo of the same code you used.
https://stackoverlfow-demos.web.app/#/
I just replaced it with wss and deployed it to the firebase hosting.
channel: HtmlWebSocketChannel.connect('wss://echo.websocket.org'),

Related

productsController is not initiated when i do hot restart , but when i do hot reload it works , flutter Getx

I am working on a ecommerce-app , I'm using Getx for the state managment, I'm a newbie with Getx a little help would be appreciated.
I am using firebase auth, so when the app is navigated from the login screen everything is fine i.e products are shown,
But when when the user are kept logged in, products are not shown until hard reloaded,
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp().then((value) {
Get.put(ProductsController());
});
runApp(MyApp());
}
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: routes,
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
return HomeScreen();
}
}
return const LoginScreen();
}),
);
controllers.dart
ProductsController productsController = ProductsController.instance;
home.dart
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(bottom: 20),
child: Column(
children: [
Text("Popular Products"),
PopularProducts(),
],
),
),
)
);
}
Popular Products
These products are shown only after hot reload, it is not shown initially, this is something to do with productController
class PopularProducts extends StatelessWidget {
const PopularProducts({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: SizedBox(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children:
productsController.products.map((ProductModel product) {
print("These are the products" + product.toString());
return ProductCard(product: product);
}).toList()),
),
)
],
);
}
The images are :
When hot restart:
When hot reload:
I faced a similar problem, my app wasn't loading the data fast enough and the widgets was already rendered, so i made a function to load the data and inside the build i used Obx() widget and called the function, but remember to but the function called with condition of the variable is empty
As #LMech replied
Obx() was missing!!
Now the code is
#override
Widget build(BuildContext context) {
return Row(
children: [
Obx( --> Add this line
() => Expanded(
child: SizedBox(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children:
productsController.products.map((ProductModel product) {
print("These are the products" + product.toString());
return ProductCard(product: product);
}).toList()),
),
),
)
],
);
}

Can't write to Firebase Real Time Database

So, i've setup up a project on Firebase and followed many tutorials on how to connect firebase to my flutter application. I started by reading FlutterFire docs, step by step, and i managed to install the FlutterFire CLI. After that, i went straight into reading the Realtime Database section and whenever i try to write into the db, nothing happens.
The database is in test mode, firebase is correctly initialised inside the project and i get no errors while trying to write inside it, so i think that even the URL is correct (but i might be wrong)
Here's the code i used:
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'firebase_options.dart'; //generated with FlutterFire CLI
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await Firebase.initializeApp(
options: Platform.isWindows ? null : DefaultFirebaseOptions.currentPlatform, //not working on windows
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final FirebaseDatabase database = FirebaseDatabase.instance;
final DatabaseReference ref = FirebaseDatabase.instance.ref("/");
void writeToFirebase() async {
print('wrote to database');
await ref.set({
'title': 'Hello World',
'body': 'This is my first post',
'userId': '123',
});
await ref.child('title').set('Hello World');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: writeToFirebase,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
I noticed that when the print() statement is above ref.set() , i see the output in the console. But, if i put it beneath the ref.set() statement, i see no output. So, this makes me think that the ref.set() does never end its execution or something like that.
I'm 2 weeks in learning flutter and i really can't make Firebase work
Since you're using await, did you consider using try-catch too so that you can catch and log any errors?
So:
void writeToFirebase() async {
print('Start writing to database');
try {
await ref.set({
'title': 'Hello World',
'body': 'This is my first post',
'userId': '123',
});
await ref.child('title').set('Hello World');
}
catch (error) {
print(error);
}
finally {
print('Done writing to database');
}
}

Redirecting without any error - displayName

While working with firebase authentication weird error without any notes happen to me.
This time application stops after I press set name button. Instantly in VScode I am redirected to this page:
As I said there is no error in debug console, no notes. No expections to see.
I guess there is something wrong with setting displayName but not clearly what.
This is full code of the class:
class Verified extends StatefulWidget {
#override
_VerifiedState createState() => _VerifiedState();
}
class _VerifiedState extends State<Verified> {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final _auth = FirebaseAuth.instance;
validate(displayName) {
if (formKey.currentState.validate()) {
setName(displayName);
}
}
setName(displayName) async {
try {
await _auth.currentUser.updateProfile(displayName: displayName);
} catch (e) {
log(e.code.toString());
}
log(_auth.currentUser.displayName.toString());
}
#override
Widget build(BuildContext context) {
return Material(
child: Padding(
padding: const EdgeInsets.all(100.0),
child: Column(
children: [
Text('choose your username'),
Form(
key: formKey,
child: TextFormField(
controller: nameController,
decoration: InputDecoration(hintText: 'name'),
),
),
RaisedButton(
child: Text('set name'),
onPressed: () => validate(nameController))
],
),
),
);
}
}
Thank you in advance
SOLUTION
When I remove from function actions with _auth.currentUser everything works, I also moved this function to the place where the user was logged in/registered and it also worked.
So as I think the error was because firebase saw no user and the solution is to use .currentUser in the same class/function as registering/logging in or saving user after those actions.

How to share provider data from streambuilder via different pages (contextes)

I want to have data from firebase in realtime on a widget. When I try to use a StreamProvider and then use Navigator.push(), the pushed widget can't get the value with Provider.of(context).
I tried putting the StreamProvider as the parent of MaterialApp. This works but the user needs to be logged in order for the Stream to get the data of the user.
I also tried using a ScopedModel. This works as well, but I don't know if this is the best approach to do this.
I would like to avoid using a global StreamProvider and would like to have an efficient solution (as little reads from firebase as possible)
main.dart
void main() => runApp(MyApp());
final GlobalKey<ScaffoldState> mainScaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> authScaffoldKey = GlobalKey<ScaffoldState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<ScreenModel>(
model: ScreenModel(),
child: MultiProvider(
providers: [
StreamProvider<User>.value(value: authService.userDoc,),
StreamProvider<bool>.value(value: authService.loading.asBroadcastStream())
],
child: MaterialApp(
title: "ListAssist",
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MainApp()
),
)
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
#override
Widget build(BuildContext context) {
User user = Provider.of<User>(context);
bool loading = Provider.of<bool>(context);
return AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: user != null ?
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(),
child: Scaffold(
key: mainScaffoldKey,
body: Body(),
drawer: Sidebar(),
),
) : Scaffold(
key: authScaffoldKey,
body: AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: loading ? SpinKitDoubleBounce(color: Colors.blueAccent) : AuthenticationPage(),
),
resizeToAvoidBottomInset: false,
)
);
}
}
class Body extends StatefulWidget {
createState() => _Body();
}
class _Body extends State<Body> {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<ScreenModel>(
builder: (context, child, model) => model.screen
);
}
}
In the Sidebar I can change to GroupView and the Provider still works.
sidebar.dart (important part)
onTap: () {
ScreenModel.of(context).setScreen(GroupView(), "Gruppen");
Navigator.pop(context);
},
The GroupView has GroupItem in it
group-item.dart (important part)
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return GroupDetail();
}),
)
When I try to use Group group = Provider.of<Group>(context); in GroupDetail or a child widget of it, it says that it cannot find any Provider for the context.
Here is the repository.
I figured out how to do it. I used a package called custom_navigator.
In sidebar.dart I changed the child when someone changes to the group view to the following:
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(user.uid),
child: CustomNavigator(
home: GroupView(),
pageRoute: PageRoutes.materialPageRoute,
)
)
With the CustomNavigator I can still use Provider.of<Group>(context) to get the data, even after a Navigator.push().

How To Make Streams in flutter dart work with provider

This was working initially and it just stopped and I cannot figure out where the problem lies.
I am working with streams on my flutter project in provider package. Streams are being emitted from services files and listening is happening on the widgets file. Firebase onAuthStateChanged stream is working but mine are not working.
I have alot of code in my files so am not going to post everything here.
I have a problem with AuthStatus stream
I tried subscribing to the stream on the widget class but it seems like no streams are getting emitted
MyApp(){
auth.authStateStream.listen((d){print("$d is data");});
}
This how firebase streams are getting emiited from services file
Stream<UserModel> get onAuthStateChanged{
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
I have a problem with AuthStatus stream. This was working initially
This is how AuthStatus stream is getting emmited from services file
//Services file
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
Stream<AuthStatus> get authStateStream{
return _currentAuthStateController.stream;
}
void testStremas() {
//Stoast.setMessage("Test Message");
_currentAuthStateController.add(AuthStatus.ACTIVE);
}
This is how provider is litening to streams as a parent of the MaterialAPP widget
class MyApp extends StatelessWidget {
//I was trying if i my widget could subscribe to the stream
MyApp (){
auth.authStateStream.listen((d){print("$d is data");});
}
final ToastHelper toast = ToastHelper();
final ThemeHelper theme = ThemeHelper();
final AuthService auth = AuthService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<UserModel>.value(value: auth.onAuthStateChanged),
StreamProvider<ToastMessage>.value(value: toast.onNewMessage),
StreamProvider<AuthStatus>.value(
value: auth.authStateStream, initialData: AuthStatus.NONE),
],
child: MaterialApp(
title: Strings.appName,
theme: theme.darkThemeData(),
home: Loader(),
routes: {
'home': (context) => Home(),
},
debugShowCheckedModeBanner: false,
),
);
}
}
This is how the above method is getting called on a the widget on a click of a button
//Widgets file
onTap: () => auth.testStremas(),
The expected result should be when the AuthStatus change from the services file, The widgets should be notified via the provider package. Thanks in advance
Widget _body(BuildContext context) {
final AuthStatus _authStatus = Provider.of<AuthStatus>(context);
return Center(
child: Container(
constraints: BoxConstraints(maxWidth: 300),
child: SingleChildScrollView(
child: Center(
child: _authStatus == AuthStatus.ACTIVE
? Padding(
padding: const EdgeInsets.all(8.0),
child:CircularProgressIndicator(strokeWidth: 2,)
)
: _buildScreen(context),
)),
),
);
}
I'm not sure - try to change
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
to
final StreamController<AuthStatus> _currentAuthStateController =
BehaviorSubject<AuthStatus>();
This BehaviorSubject from rxdart library https://pub.dev/packages/rxdart, so, you should import it. BehaviorSubject is keep last state of stream. You can read more here https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html
import 'package:rxdart/rxdart.dart';

Resources