Set value of a switch with Firestore boolean - firebase

I'm making a test app that switches a boolean value in Firestore. I need the app to retrieve the initial value of the switch from Firestore. Through my method, I get the error type 'Future' is not a subtype of type 'bool'. The Boolean value is called LEDOn. Here is my app:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
final db = Firestore.instance;
void updateLED(bool newValue) async {
await db
.collection('LEDStatus')
.document('LEDStatus')
.updateData({'LEDOn': newValue});
}
Future<dynamic> checkLEDStatus() async {
DocumentSnapshot snapshot =
await db.collection('LEDStatus').document('LEDStatus').get();
return snapshot.data['LEDOn'];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.dark),
home: Scaffold(
appBar: AppBar(
title: Text('Thingspeak'),
),
body: Column(
children: <Widget>[
Switch(
value: checkLEDStatus(),
onChanged: (bool newValue) {
setState(() {
print(newValue);
updateLED(newValue);
});
})
],
),
),
);
}
}

Use a FutureBuilder. Also, change your checkLEDStatus() return type to Future<bool>.
FutureBuilder(
future: checkLEDStatus(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
bool result = snapshot.data;
return Switch(value: result, onChanged: (bool newVal) {
setState(() => result = newVal);
updateLED(newVal);
});
} else {
return Center(child: CircularProgressIndicator());
}
}
)

The Switch widget has the property value which takes a value of type bool. Currently in your code you are returning a Future and that's why you get that error. It should be like this:
Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
});
},
You can retrieve the boolean value from firestore inside the initState and then assign it to the property value. Then when the user switches inside setState() call the update method and do isSwitched = newValue

Related

Flutter and firestore web app - Assertion failed: Map =! null is not true

I'm trying to create a chart on a Flutter web app by accessing data from Cloud Firestore.
However, it can't seem to extract and map the details from Firestore.
Firebase console
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:inglesy/items.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class Home extends StatefulWidget {
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<charts.Series<Item, String>>? _seriesBarData; //Try (dynamic,String)
List<Item>? myData;
_generateData(myData) {
print("_generateData worked");
_seriesBarData?.add(
charts.Series(
domainFn: (Item item, _) => item.itemstring.toString(),
measureFn: (Item item, _) => item.itemvotes,
id: 'Items',
data: myData,
),
);
}
#override
Widget build(BuildContext context) {
print("returning AppBar/scaffold now");
return Scaffold(
appBar: AppBar(
title: const Text("This is a title."),
foregroundColor: Colors.pink,
),
body: _buildBody(context),
);
}
Widget _buildBody(context) {
print("Doing _buildBody now");
final Stream<QuerySnapshot> _userStream =
FirebaseFirestore.instance.collection("poll").snapshots();
return StreamBuilder<QuerySnapshot>(
stream: _userStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
} else {
List<Item> item = snapshot.data!.docs
.map((DocumentSnapshot document) =>
Item.fromMap(document.data() as Map<String, dynamic>))
.toList();
return _buildChart(context, item);
}
},
);
}
Widget _buildChart(BuildContext context, List<Item> item) {
myData = item;
_generateData(myData);
return Padding(
padding: EdgeInsets.all(8.0),
child: Container(
child: Center(
child: Column(
children: [
Text("This is a text"),
SizedBox(height: 10.0),
Expanded(
child: charts.BarChart(
_seriesBarData!,
animate: true,
animationDuration: const Duration(seconds: 2),
),
)
],
),
),
),
);
}
}
items.dart
class Item {
final String? itemstring;
final int? itemvotes;
Item({this.itemstring, this.itemvotes});
Item.fromMap(Map<String, dynamic> map)
: assert(map['itemstring'] != null),
assert(map['itemvotes'] != null),
itemstring = map['itemstring'],
itemvotes = map['itemvotes'];
#override
String toString() {
return "Item string: $itemstring | Item votes: $itemvotes";
}
}
It shows this error
PS, I've already done the necessary set-up i.e. I've already installed Firebase CLI and have it generated firbase_options.dart
PPS, I have also already set up Firebase (anonymous) authentication and it works with no errors. But for now, I'm not using it and I'm automatically running home.dart to focus on the Firebase database aspect.
Why don’t you try making the Item.fromMap method a regular factory method like:
factory Item.fromMap(Map<String, dynamic> map) {
return Item(
itemstring = map['itemstring'] ?? '',
itemvotes = map['itemvotes'] ?? ''
);
}

Pass fetched value to a firestore reference to flutter's streambuilder

I'm accessing a user's favorite group which is inside groupfav in Firestore, when I get it I want to give it as part of the reference to the streambuilder stream:, so that it knows what to show in a list, but I can't pass the variable that contains the favorite group, what should I do or what am I doing wrong?
static String? userID = FirebaseAuth.instance.currentUser?.uid; // get current user id
static var taskColeccion = FirebaseFirestore.instance.collection("usuarios");
var tack = taskColeccion.doc("$userID").get().then((value) {
var groupfav = value.data()!["groupfav"]; // value i get from firestore
return groupfav;
});
late Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
photo of firestore
The photo shows how Firestore's logic is and the value marked in green is what I must pass to the late Stream<QuerySnapshot> task... in its reference, logically it is a random value that I would not know. thanks for any help!
this is what the code looks like now (I took things that were not important)
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
final tack = taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
automaticallyImplyLeading: false,
),
body: StreamBuilder(
stream: task,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text("error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("cargando");
}
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("${data.docs[index]['titulo']}"),
subtitle: Text("${data.docs[index]['contenido']}"),
onTap: () {},
trailing: IconButton(
icon: const Icon(Icons.delete),
color: Colors.red[200],
onPressed: () {
// delete function
},
),
),
);
},
);
},
),
);
}
}
You just need to declare groupfav outside of the scope of the get method of taskColeccion;
The way you have it, the variable no longer exists by the time you're trying to pass it into the task stream.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
late Stream<QuerySnapshot> task;
#override
void initState() {
super.initState();
taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
}

Flutter/Firebase: Dynamic homepage depending on user loginStatus using MultiProvider issues

I want to check if the user is already logged in and show him page depending on that.
Here is my main.dart:
...
import 'firebase/authentication_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
initialData: null,
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
backgroundColor: Colors.transparent,
primaryColor: Color(0xff4d629f),
buttonBarTheme:
ButtonBarThemeData(alignment: MainAxisAlignment.center)),
home: AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
if (firebaseUser != null) {
//If the user is successfully Logged-In.
return HomePage();
} else {
//If the user is not Logged-In.
return LoginPage();
}
}
}
And here is my Authentication_service.dart:
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
UserModel userModel = UserModel.empty();
final userRef = FirebaseFirestore.instance.collection('users');
AuthenticationService(this._firebaseAuth);
Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();
Future<String> signIn(
{required String email, required String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
return "Signed in";
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found')
return "There is no user for that e-mail";
else if (e.code == 'wrong-password')
return "Entered wrong Password";
else
return "Something went wrong: $e";
}
}
...
And there are errors:
The following ProviderNotFoundException was thrown building AuthenticationWrapper(dirty):
Error: Could not find the correct Provider above this AuthenticationWrapper Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that AuthenticationWrapper is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
Unfortunately it didn't help or i just can't implement it in a right way.
#SumerSingh solution worked, i just changed it a bit for my use.
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _initialized = false;
bool _error = false;
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch (e) {
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
debugShowCheckedModeBanner: false,
home: Scaffold(
body: _error
? splashScreen()
: !_initialized
? splashScreen()
: SplashScreen()));
}
}
class SplashScreen extends StatefulWidget {
SplashScreen({Key? key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
var currentUser;
AuthenticationService _authService =
new AuthenticationService(FirebaseAuth.instance);
late final UserModel userModel;
bool isAuthinticated = false;
_isUserSignedin() async {
currentUser = _auth.currentUser;
userModel = await _authService.getUserFromDB(uid: _auth.currentUser!.uid);
setState(() {
currentUser != null ? isAuthinticated = true : isAuthinticated = false;
});
}
#override
void initState() {
super.initState();
_isUserSignedin();
startTime();
}
startTime() async {
var _duration = new Duration(seconds: 4);
return new Timer(_duration, navigationPage);
}
Widget userAuthState() {
if (!isAuthinticated)
return LoginPage();
else if (userModel.type == 'Attendant')
return AttendantMainPage();
else
return SeniorMainPage();
}
void navigationPage() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) => userAuthState()),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(body: splashScreen());
}
}
Widget splashScreen() {
return Container(
height: double.maxFinite,
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
CircleAvatar(
radius: 80.0, child: Image.asset('assets/logo.png')),
Text("APP NAME",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
CircularProgressIndicator()
]),
);
}
It works well, thank you for help!

The argument type 'Future<dynamic>' can't be assigned to the parameter type 'String'

I've seen similar questions that were asked in regards to this but my problem is a little different in that I'm keeping my application modular so I have defined the following method in a different dart file(Simply a class, not a widget):
Future getProfile() async {
return await usersCollection.doc(uid).get().then<dynamic>((DocumentSnapshot snapshot) async {
print(snapshot.data()['name']);
if(snapshot.data()['name'] == null){
print("No name exists");
}
else {
return snapshot.data()['name'];
}
});
And I'm trying to use it's value on my home widget:
import 'package:flutter/material.dart';
import 'package:carpoolapp/services/auth.dart';
import 'package:carpoolapp/services/database.dart';
import 'package:firebase_auth/firebase_auth.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final AuthService _auth = AuthService();
User user = FirebaseAuth.instance.currentUser;
DatabaseService db = DatabaseService(uid: FirebaseAuth.instance.currentUser.uid);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.redAccent,
appBar: AppBar(
title: Text('Signed in'),
backgroundColor: Colors.blueAccent,
elevation: 0.0, //no drop shadow
actions: <Widget>[
FlatButton.icon(
onPressed: () async {
await _auth.signOutUser();
},
icon: Icon(Icons.person),
label: Text('logout')),
],
),
body: Text(db.getProfile()), // Error is here
//body: UserTile(user: FirebaseAuth.instance().getCurrentUser()),
);
}
}
How do I go about making this work without sacrificing the modularity?
By seeing
The argument type 'Future<dynamic>' can't be assigned to the parameter type 'String'
this and
Text(db.getProfile())
the issue is db.getProfile() is an async method. That's why its telling Future can't be assigned to String since Text widget data key is of type String not Future<String>.
You can use FutureBuilder in the body and use the snapshot in the Text which will have the String value.
I would like to add to #Pradyot Prakash's answer with some actual code:
Use the following code snippet as an example to achieve the modularity
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<DocumentSnapshot<Map<String, dynamic>>>(
future: getProfile() // πŸ‘ˆ Your future function here
builder: (_, snapshot) {
if (snapshot.hasError) return Text('Error = ${snapshot.error}');
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data = snapshot.data!.data()!;
return Text(data['name']); //πŸ‘ˆ Your valid data here
},
)),
);
}

Flutter video_player with URL from Firestore Document

I'm trying to play a video from a URL of a Firestore Document. To play a video in Flutter, I have to instantiate its Url in the init() method. I set a default URL to a butterfly video, and the value was supposed to be replaced by the URL obtained from Firestore. (So that it is easy for me to see if the code works). However, the code does not work properly. I got an error that says "NoSuchMethodError: The getter 'value' was called on null".
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// Create the initialization Future outside of build
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
// Check for error
if (snapshot.hasError) {
print(snapshot.error);
return Center(
child: Container(
child: Text(
"Something went wrong",
textDirection: TextDirection.ltr,
),
),
);
}
//Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
return MaterialApp(
title: 'Flutter Demo',
home: VideoPlayerScreen(),
);
}
return CircularProgressIndicator();
});
}
}
class VideoPlayerScreen extends StatefulWidget {
#override
_VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
VideoPlayerController _controller;
Future<void> _initializeVideoPlayerFuture;
FirebaseFirestore firestore = FirebaseFirestore.instance;
String videoUrl =
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4';
#override
void initState() {
firestore.collection("videos").get().then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
// _controller.dispose();
videoUrl = doc["videoUrl"];
_controller = VideoPlayerController.network(videoUrl);
_initializeVideoPlayerFuture = _controller.initialize();
print(videoUrl);
})
});
// _controller = VideoPlayerController.network(videoUrl);
// _initializeVideoPlayerFuture = _controller.initialize();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Video Player"),
),
body: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Column(
children: [
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
],
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
Try the following:
#override
void initState() {
super.initState();
firestore.collection("videos").get().then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
videoUrl = doc["videoUrl"];
_controller = VideoPlayerController.network(videoUrl);
_initializeVideoPlayerFuture = _controller.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
});
});
}
Since initialize() is asynchronous, then you can use the method then which will get called when the future completes. Inside the callback, you can call setState() which will trigger a rebuild and notify the framework that the internal state of the widgets has changed .
https://pub.dev/packages/video_player

Resources