Flutter web 3.0 blog app with Firebase, Provider – Sanjib Sinha
Mục Lục
Flutter web 3.0 blog app with Firebase, Provider
While building the Flutter web 3.0 blog app with Firebase and Provider, we have faced some challenges.
Firstly, we cannot hard code the blog posts anymore.
Secondly, we have to assure that only the signed-in visitors will post their blogs.
Finally, we will not use the multi provider technique. Instead we will use the ChangeNotifierProvider class.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'model/state_of_application.dart';
import 'view/chat_app.dart';
/// moving to first branch
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => StateOfApplication(),
builder: (context, _) => const ChatApp(),
),
);
}
For instance, Flutter hard code is just like any other hard code principle that we cannot follow when we develop a dynamic flutter app. Right?
Why?
Because in our case, we’re dealing with a remote database server Firebase and Firestore.
As a result, particularly at this stage, users will insert data, and retrieve data from Firebase.
On the contrary, we embed hard code into our source code. Neither it comes from any external source, nor we change the value on runtime. Right?
For example, we’ve seen the same instance in our Firebase, Firestore and Provider web app.
What do we see on the screen?Let’s first see the home page of our flutter web app.
User can select a portion of text with selectable text in flutter
But after the initial change our current home page will look like the following.
Flutter web 3.0 homepage
As we see, here the first step is either you have to sign in, or you can register so that later you can sign in.
As an outcome, the Firebase authentication page will show the existing users who have registered already.
Flutter web 3.0 Firebase authentication page
However, as we were discussing flutter 3.0 web app, initially it was different.
Why so? Because we had hard coded the initial blog data.
There was no flutter sign in method so that the user could log in.
It’s true that we had not introduced Firebase and Flutter sign in methods in our previous discussion.
How Flutter web 3.0 works with Firebase, and Provider
While talking about Flutter 3.0, we have seen what are the primary changes.
Firstly, with reference to mobile application development, there has not been a great change. Structurally what we have been doing, will continue to do.
Secondly, we can work with Firebase and Provider just like before.
For that reason, we need to add the package dependencies first to the pubspec.yaml file.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
firebase_core: ^1.14.0
firebase_auth: ^3.3.13
cloud_firestore: ^3.1.11
google_fonts: ^2.3.1
provider: ^6.0.2
As for the next move, we need to add the state management process in our model folder.
This class will extend the Changenotifier class and initialize the state.
Since our data source is external, we will use Future, async and awit.
In this class we will define different types of methods. It will check whether the user is new or existing.
Based on that, new users can register as follows.
Flutter web 3.0 sign in, register page
By the way, we can take a look at the code where it also checks whether the user is logged in or not.
On the other hand it will also add the blog posts to the Firebase Firestore collection.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../controller/authenticate_to_firebase.dart';
import '../firebase_options.dart';
import '../view/let_us_chat.dart';
class StateOfApplication extends ChangeNotifier {
StateOfApplication() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = UserStatus.loggedIn;
_chatBookSubscription = FirebaseFirestore.instance
.collection('blog')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_chatBookMessages = [];
for (final document in snapshot.docs) {
_chatBookMessages.add(
LetUsChatMessage(
name: document.data()['name'] as String,
title: document.data()['title'] as String,
body: document.data()['body'] as String,
),
);
}
notifyListeners();
});
} else {
_loginState = UserStatus.loggedOut;
_chatBookMessages = [];
}
notifyListeners();
});
}
UserStatus _loginState = UserStatus.loggedOut;
UserStatus get loginState => _loginState;
String? _email;
String? get email => _email;
StreamSubscription<QuerySnapshot>? _chatBookSubscription;
StreamSubscription<QuerySnapshot>? get chatBookSubscription =>
_chatBookSubscription;
List<LetUsChatMessage> _chatBookMessages = [];
List<LetUsChatMessage> get chatBookMessages => _chatBookMessages;
void startLoginFlow() {
_loginState = UserStatus.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = UserStatus.password;
} else {
_loginState = UserStatus.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = UserStatus.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
Future<DocumentReference> addMessageToChatBook(String title, String body) {
if (_loginState != UserStatus.loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance.collection('blog').add(<String, dynamic>{
'title': title,
'body': body,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
}
At the same time we also define the nature of the Flutter 3.0 application.
While adding the project in Firebase, we need to choose the platform. According to that it creates the API keywords.
Now in our code we can supply those keywords and connect with Firebase.
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// we need to specify the associated values according to the platform
/// we're using, like in this case, we have chosen web platform
/// in Firebase console
///
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
//return web;
}
// ignore: missing_enum_constant_in_switch
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
}
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: "****************************************",
appId: "*******************************************",
messagingSenderId: "***********",
projectId: "*********",
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: '',
appId: '',
messagingSenderId: '',
projectId: '',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: '',
appId: '',
messagingSenderId: '',
projectId: '',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: '',
appId: '',
messagingSenderId: '',
projectId: '',
);
}
Certainly, in our case, we have chosen the web platform.
As a result, now we can see the titles of the blogs.
Flutter web 3.0 all posts displaying titles
Flutter web 3.0 and Firestore database
As our Flutter web 3.0 Firebase Provider blog app has built a connection with Firestore database, users can insert data.
Once the data is into the external database, it is not difficult to navigate to the blog detail page.
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import '../controller/all_widgets.dart';
import '../controller/authenticate_to_firebase.dart';
import '../model/state_of_application.dart';
import 'let_us_chat.dart';
class ChatHomePage extends StatelessWidget {
const ChatHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Firebase Blog'),
),
body: ListView(
children: <Widget>[
Image.network(
'https://cdn.pixabay.com/photo/2018/03/24/00/36/girl-3255402_960_720.png',
width: 250,
height: 250,
fit: BoxFit.cover,
),
const SizedBox(height: 8),
Consumer<StateOfApplication>(
builder: (context, appState, _) => AuthenticationForFirebase(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.signOut,
),
),
const Paragraph(
'Hi, I\'m Angel, I\'m Inviting you to write Blogs. Please join me.'),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header('Write your Blog'),
const Paragraph(
'Join your friends and write your blog!',
),
Consumer<StateOfApplication>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == UserStatus.loggedIn) ...[
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LetUsChat(
addMessageOne: (title, body) =>
appState.addMessageToChatBook(title, body),
messages: appState.chatBookMessages,
),
),
);
},
child: Text(
'Let\'s Blog',
style: GoogleFonts.laila(
fontSize: 30.0,
fontWeight: FontWeight.bold,
color: Colors.yellow,
backgroundColor: Colors.red,
),
),
),
],
],
),
),
],
),
);
}
}
Before we have done a lot of such things.
Sending data through the class constructor.
But it would not be possible, if we had not used ChangeNotifierProvider, ChangeNotifier, and Consumer from the Provider package.
After all, we are sending data from the parent widget to the child widget. In addition, we need to manage the state of the application.
Incidentally the child widget will now consume the data and display the blog detail page.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import '../controller/all_widgets.dart';
import '../controller/authenticate_to_firebase.dart';
import '../model/state_of_application.dart';
class LetUsChatMessage {
LetUsChatMessage({
required this.name,
required this.title,
required this.body,
});
final String name;
final String title;
final String body;
}
class LetUsChat extends StatefulWidget {
const LetUsChat({
required this.addMessageOne,
required this.messages,
});
final FutureOr<void> Function(String messageOne, String messageTwo)
addMessageOne;
final List<LetUsChatMessage> messages;
@override
_LetUsChatState createState() => _LetUsChatState();
}
class _LetUsChatState extends State<LetUsChat> {
final _formKey = GlobalKey<FormState>(debugLabel: '_LetUsBlog');
final _controllerOne = TextEditingController();
final _controllerTwo = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Firebase Blog'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: TextFormField(
controller: _controllerOne,
decoration: const InputDecoration(
hintText: 'title',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 10),
Expanded(
child: TextFormField(
controller: _controllerTwo,
decoration: const InputDecoration(
hintText: 'Body',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 10),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessageOne(
_controllerOne.text, _controllerTwo.text);
_controllerOne.clear();
_controllerTwo.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 6),
Text('SUBMIT'),
],
),
),
for (var message in widget.messages)
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlogDetailScreen(
name: message.name,
title: message.title,
body: message.body,
),
),
);
},
child: Paragraph('${message.name}: ${message.title}'),
),
],
),
),
),
);
}
} // LetUsChat state ends
class BlogDetailScreen extends StatelessWidget {
// static const routename = '/product-detail';
const BlogDetailScreen({
Key? key,
required this.name,
required this.title,
required this.body,
}) : super(key: key);
final String name;
final String title;
final String body;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(name),
),
body: SingleChildScrollView(
child: Consumer<StateOfApplication>(
builder: (context, appState, _) => Column(
children: <Widget>[
if (appState.loginState == UserStatus.loggedIn) ...[
SizedBox(
height: 300,
width: double.infinity,
child: Image.network(
'https://cdn.pixabay.com/photo/2018/03/24/00/36/girl-3255402_960_720.png',
width: 250,
height: 250,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
title,
style: GoogleFonts.aBeeZee(
fontSize: 60.0,
fontWeight: FontWeight.bold,
color: Colors.yellow,
backgroundColor: Colors.red,
),
),
const SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
body,
textAlign: TextAlign.center,
softWrap: true,
style: GoogleFonts.aBeeZee(
fontSize: 30.0,
fontWeight: FontWeight.bold,
color: Colors.black26,
backgroundColor: Colors.lightBlue[300],
),
),
),
const SizedBox(
height: 10,
),
],
],
),
),
),
);
}
}
Finally we can see the blog that Angel has just posted.
Flutter web 3.0 blog detail page
At the same time the same blog post is present at the Firestore database collection.
Flutter web 3.0 Firebase Firestore database shows the blog posts
However, we are not happy with the design part. Therefore we will introduce Material Design 3 and change the entire look of the Flutter web 3.0 Firebase Provider blog app.
So stay tuned.
What Next?
Books at Leanpub
Books in Apress
My books at Amazon
Courses at Educative
GitHub repository
Python and Data Science
Share this:
Like this:
Like
Loading…