Cat App, A Flutter Mini Project
Flutter Cats App
A simple flutter application to share your cat pictures.
Features
- Personalized Profile Name, Picture and Bio.
- Gallery to view the Cat Pictures.
- Like and Unlike the Cat Picture.
Dependencies
pubspec.yml
name: cat_api
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
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
http: ^0.13.3
bloc: ^7.0.0
flutter_bloc: ^7.0.1
lazy_load_scrollview: ^1.3.0
http_auth: ^1.0.0
flutter_spinkit: ^5.0.0
like_button: ^2.0.2
cached_network_image: ^3.0.0
firebase_core: ^1.2.1
flutter_signin_button: ^2.0.0
firebase_auth: ^1.3.0
flutter_facebook_auth: ^3.4.1
google_sign_in: ^5.0.4
shared_preferences: ^2.0.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_native_splash: ^1.1.8+4
flutter_native_splash:
color: "#ffffff"
color_dark: "#1a1a1a"
image: assets/cat.png
image_dark: assets/cat.png
andriod: true
ios: true
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/images/placeholder.png
Source Code
- Create a flutter app using
flutter create Cat App
. - Go inside the
Cat App
folder and inside thelib
folder add the following folder, files and .dart files. lib/main.dart
import './bloc/bloc_authentification/bloc.dart';
import './bloc/cat_facts/bloc.dart';
import './bloc/cat_images/bloc.dart';
import './bloc/cat_like/bloc.dart';
import './features/home/home_1.dart';
import './features/home/home_2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:firebase_core/firebase_core.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
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) {
if (snapshot.hasError) {
return MaterialApp(home: Text('Error connecting to firebase'));
} else if (snapshot.connectionState == ConnectionState.done) {
return MultiBlocProvider(
providers: [
BlocProvider<CatsBloc>(
create: (context) => CatsBloc()..add(InitialCats())),
BlocProvider<CatFactsBloc>(
create: (context) => CatFactsBloc()..add(FactsLoaded())),
BlocProvider<LikeBloc>(create: (context) => LikeBloc()),
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
],
child: BlocBuilder<AuthBloc, AuthState>(
buildWhen: (previous, current) {
return true;
}, builder: (context, state) {
if (state is AuthSuccess) {
if (state.currentUser != null) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Cats App',
home: HomeScreenUser(),
);
} else {
return Login();
}
} else {
return Login();
}
}),
);
} else {
return CircularProgressIndicator();
}
});
}
}
lib/bloc/bloc_authentification/bloc.dart
import 'dart:async';
import 'package:cat_api/utils/auth.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:meta/meta.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
part 'event.dart';
part 'state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final authService = AuthService();
final fb = FacebookAuth.instance;
final google = GoogleSignIn(
scopes: ['email', 'https://www.googleapis.com/auth/contacts.readonly']);
AuthBloc()
: super(AuthService().user != null
? AuthSuccess(currentUser: AuthService().user)
: NoAuth());
@override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async* {
if (event is FacebookLoginEvent) {
final res =
await fb.login(permissions: ['public_profile', 'user_photos']);
if (res.status == LoginStatus.success) {
final credentials =
FacebookAuthProvider.credential(res.accessToken!.token);
final user = await authService.signInWithCredentials(credentials);
yield AuthSuccess(currentUser: user.user);
} else {
yield (NoAuth());
}
} else if (event is LogOutEvent) {
await fb.logOut();
await google.signOut();
yield NoAuth();
} else if (event is GoogleLoginEvent) {
try {
final googleUser = await google.signIn();
final googleAuth = await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
idToken: googleAuth?.idToken, accessToken: googleAuth?.accessToken);
final user = await authService.signInWithCredentials(credential);
yield AuthSuccess(currentUser: user.user);
} catch (error) {}
}
}
}
lib/bloc/bloc_authentification/event.dart
part of 'bloc.dart';
@immutable
abstract class AuthEvent {}
class FacebookLoginEvent extends AuthEvent {}
class GoogleLoginEvent extends AuthEvent {}
class LogOutEvent extends AuthEvent {}
lib/bloc/bloc_authentification/state.dart
part of 'bloc.dart';
@immutable
abstract class AuthState {
final User? currentUser = null;
}
class AuthSuccess extends AuthState {
final User? currentUser;
AuthSuccess({required this.currentUser});
}
class NoAuth extends AuthState {
final User? currentUser = null;
}
lib/bloc/cat_facts/bloc.dart
import 'dart:async';
import 'dart:convert';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:http/http.dart' as http;
part 'event.dart';
part 'state.dart';
class CatFactsBloc extends Bloc<CatFactsEvent, CatFactsState> {
CatFactsBloc() : super(CatFactsInitial());
@override
Stream<CatFactsState> mapEventToState(
CatFactsEvent event,
) async* {
if (event is FactsLoaded) {
final facts = await loadFacts();
yield CatsFactsLoaded(facts: facts);
}
}
}
Future<http.Response> getData(String uri, Map<String, String> headers) async {
return await http.get(Uri.parse(uri));
}
Future<List<String>> loadFacts() async {
var response = await getData('https://catfact.ninja/facts?limit=3', {});
if (response.statusCode == 200) {
List<dynamic> data = json.decode(response.body)['data'];
return data.length > 0
? data.map((i) => i['fact'].toString()).toList()
: [];
} else {
return [];
}
}
lib/bloc/cat_facts/event.dart
part of 'bloc.dart';
@immutable
abstract class CatFactsEvent {}
class FactsLoaded extends CatFactsEvent {}
lib/bloc/cat_facts/state.dart
part of 'bloc.dart';
@immutable
abstract class CatFactsState {}
class CatFactsInitial extends CatFactsState {}
class CatsFactsLoaded extends CatFactsState {
final List<String> facts;
CatsFactsLoaded({required this.facts});
}
lib/bloc/cat_images/bloc.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
part 'event.dart';
part 'state.dart';
class CatsBloc extends Bloc<CatsEvent, CatsState> {
final List<ImagesData> _images = [];
CatsBloc() : super(CatsInitial());
@override
Stream<CatsState> mapEventToState(
CatsEvent event,
) async* {
if (event is InitialCats) {
final catsImages = await loadImages();
_images..addAll(catsImages);
yield CatsLoaded(catsImages: _images);
} else if (event is LoadNewImages) {
final catsImages = await loadImages();
_images..addAll(catsImages);
yield CatsLoaded(catsImages: _images);
}
}
}
Future<http.Response> getData(String uri, Map<String, String> headers) async {
return await http.get(Uri.parse(uri), headers: headers);
}
const CACHED_IMAGES = 'CACHED_IMAGES';
Future<List<ImagesData>> loadImages() async {
final sharedPreference = await SharedPreferences.getInstance();
try {
var response = await getData(
'https://api.thecatapi.com/v1/images/search?limit=20',
{'x-api-key': 'd12e1ec7-64e0-4026-bfa0-f8336b070970'});
if (response.statusCode == 200) {
List<dynamic> data = json.decode(response.body);
List<ImagesData> list = data.length > 0
? data
.map((e) =>
ImagesData(url: e['url'].toString(), id: e['id'].toString()))
.toList()
: [];
sharedPreference.setStringList(
CACHED_IMAGES,
data
.map(
(e) => json.encode({'url': e['url'], 'id': e['id']}),
)
.toList());
return list;
} else {
return [];
}
} on SocketException {
final cachedData = sharedPreference.getStringList(CACHED_IMAGES);
if (cachedData == null) {
return [];
}
return Future.value(cachedData.map((e) {
final decodedImage = json.decode(e);
return ImagesData(url: decodedImage['url'], id: decodedImage['id']);
}).toList());
} catch (error) {
return [];
}
}
class ImagesData {
String url;
String id;
ImagesData({
required this.url,
required this.id,
});
}
lib/bloc/cat_images/event.dart
part of 'bloc.dart';
@immutable
abstract class CatsEvent {}
class InitialCats extends CatsEvent {}
class LoadNewImages extends CatsEvent {}
lib/bloc/cat_images/state.dart
part of 'bloc.dart';
@immutable
abstract class CatsState {}
class CatsInitial extends CatsState {}
class CatsLoaded extends CatsState {
final List<ImagesData> catsImages;
CatsLoaded({required this.catsImages});
}
lib/bloc/cat_like/bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'event.dart';
part 'state.dart';
class LikeBloc extends Bloc<LikeEvent, LikesState> {
final Set<String> _likes = {};
LikeBloc() : super(Likes(likes: {}));
@override
Stream<LikesState> mapEventToState(
LikeEvent event,
) async* {
if (event is Like) {
_likes.add(event.id);
yield Likes(likes: _likes);
} else if (event is Dislike) {
_likes.remove(event.id);
yield Likes(likes: _likes);
}
}
}
lib/bloc/cat_like/event.dart
part of 'bloc.dart';
@immutable
abstract class LikeEvent {}
class Like extends LikeEvent {
final String id;
Like({required this.id});
}
class Dislike extends LikeEvent {
final String id;
Dislike({required this.id});
}
lib/bloc/cat_like/state.dart
part of 'bloc.dart';
@immutable
abstract class LikesState {}
class Likes extends LikesState {
final Set<String> likes;
Likes({required this.likes});
}
lib/cached/cached_network_images.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CachedImage extends StatelessWidget {
final String imageUrl;
const CachedImage({Key? key, required this.imageUrl}) : super(key: key);
Widget _imageWidget(ImageProvider imageProvider) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
);
}
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
imageBuilder: (context, imageProvider) => _imageWidget(imageProvider),
placeholder: (context, url) =>
Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) =>
_imageWidget(AssetImage('assets/images/placeholder.png')));
}
}
lib/features/cats/cats_screen.dart
import '/bloc/cat_like/bloc.dart';
import '/features/cats/detail_page.dart';
import '/cached/cached_network_images.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '/bloc/cat_images/bloc.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
import 'package:like_button/like_button.dart';
class CatsScreen extends StatelessWidget {
const CatsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
LikeBloc _likeBloc = BlocProvider.of<LikeBloc>(context);
CatsBloc _catsBloc = BlocProvider.of<CatsBloc>(context);
return BlocBuilder<CatsBloc, CatsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
if (state is CatsLoaded) {
return LazyLoadScrollView(
onEndOfPage: () {
_catsBloc.add(LoadNewImages());
},
child: GridView.builder(
padding: EdgeInsets.all(3),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 1,
crossAxisSpacing: 1,
childAspectRatio: 1,
),
itemCount: state.catsImages.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return HeroScreen(
imgUrl: '${state.catsImages[index].url}',
tag: 'dash${state.catsImages[index].id}',
);
}));
},
child: Hero(
tag: 'dash${state.catsImages[index].id}',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
return SingleChildScrollView(
child: Container(
margin: EdgeInsets.all(30),
child: Image.network(
'${state.catsImages[index].url}'),
),
);
},
child: Stack(
children: [
CachedImage(imageUrl: state.catsImages[index].url),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BlocBuilder<LikeBloc, LikesState>(
builder: (context, likeState) {
if (likeState is Likes) {
return LikeButton(
onTap: (isLiked) async {
if (likeState.likes.contains(state
.catsImages[index].url)) {
_likeBloc.add(Dislike(
id: state.catsImages[index]
.url));
return true;
} else {
_likeBloc.add(Like(
id: state.catsImages[index]
.url));
return false;
}
},
size: 32,
circleColor: CircleColor(
start: Colors.red,
end: Colors.red),
bubblesColor: BubblesColor(
dotPrimaryColor: Colors.red,
dotSecondaryColor: Colors.red,
),
likeBuilder: (_) {
return Icon(
CupertinoIcons.heart_fill,
color: likeState.likes.contains(
state.catsImages[index]
.url)
? Colors.red
: Colors.white70,
size: 32,
);
},
);
} else {
return Text(
"Something went wrong...");
}
}),
],
),
],
),
),
],
),
),
);
}),
);
} else {
return Center(
child: SpinKitFadingCircle(
size: 100,
itemBuilder: (BuildContext context, int index) {
return DecoratedBox(
decoration: BoxDecoration(
color: index.isEven ? Colors.blue : Colors.blue[300],
borderRadius: BorderRadius.circular(180),
),
);
},
),
);
}
});
}
}
lib/features/cats/detail_page.dart
import 'package:cached_network_image/cached_network_image.dart';
import '/bloc/cat_facts/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class HeroScreen extends StatelessWidget {
final String imgUrl;
final String tag;
const HeroScreen({Key? key, required this.imgUrl, required this.tag})
: super(key: key);
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[Colors.blue, Colors.purple])),
),
),
body: Center(
child: Hero(
tag: tag,
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
return SingleChildScrollView(
child: CachedNetworkImage(
imageUrl: '$imgUrl',
),
);
},
child: Container(
decoration: BoxDecoration(),
child: Column(
children: [
CachedNetworkImage(
imageUrl: '$imgUrl',
),
// ListTile(leading: Icon(Icons.favorite, color: Colors.red)),
Expanded(
flex: 1,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Flexible(
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Facts()),
),
),
),
],
),
),
),
),
),
);
}
}
class Facts extends StatelessWidget {
const Facts({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: BlocBuilder<CatFactsBloc, CatFactsState>(
builder: (context, state) {
if (state is CatsFactsLoaded) {
return Column(
children: state.facts
.map(
(e) => Text('«$e»',
style: TextStyle(
color: Colors.blue.withOpacity(0.6),
fontSize: 20,
fontWeight: FontWeight.w500,
)),
)
.toList(),
);
} else {
return Center(
child: SpinKitFadingCircle(
size: 50,
itemBuilder: (BuildContext context, int index) {
return DecoratedBox(
decoration: BoxDecoration(
color: index.isEven ? Colors.blue : Colors.blue,
borderRadius: BorderRadius.circular(100),
),
);
},
),
);
}
},
),
);
}
}
lib/features/favourite/favourite.dart
import '/bloc/cat_like/bloc.dart';
import '/cached/cached_network_images.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:like_button/like_button.dart';
class Favorites extends StatelessWidget {
const Favorites({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
LikeBloc _bloc = BlocProvider.of<LikeBloc>(context);
return BlocBuilder<LikeBloc, LikesState>(
builder: (context, state) {
if (state is Likes) {
return GridView.builder(
padding: EdgeInsets.all(3),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 1,
crossAxisSpacing: 1,
childAspectRatio: 1,
),
itemCount: state.likes.length,
itemBuilder: (context, index) => Stack(
children: [
CachedImage(imageUrl: state.likes.elementAt(index)),
Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BlocBuilder<LikeBloc, LikesState>(
builder: (context, likeState) {
if (likeState is Likes) {
return LikeButton(
onTap: (isLiked) async {
_bloc.add(Dislike(
id: state.likes.elementAt(index)));
return !isLiked;
},
circleColor: CircleColor(
start: Colors.red, end: Colors.red),
bubblesColor: BubblesColor(
dotPrimaryColor: Colors.red,
dotSecondaryColor: Colors.red,
),
size: 32,
likeBuilder: (bool isLiked) {
return Icon(
Icons.favorite,
color:
isLiked ? Colors.white70 : Colors.red,
size: 30,
);
});
} else {
return Text("Something went wrong...");
}
}),
],
)
],
),
),
],
),
);
} else {
return Text('Loading likes..');
}
},
);
}
}
lib/features/home/home_1.dart
import '/bloc/bloc_authentification/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_signin_button/button_list.dart';
import 'package:flutter_signin_button/button_view.dart';
class Login extends StatelessWidget {
const Login({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final _bloc = BlocProvider.of<AuthBloc>(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Cats App'),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[Colors.blue, Colors.purple])),
),
),
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SignInButton(
Buttons.Facebook,
onPressed: () {
_bloc.add(FacebookLoginEvent());
},
),
SignInButton(
Buttons.Google,
onPressed: () {
_bloc.add(GoogleLoginEvent());
},
),
],
),
),
),
);
}
}
lib/features/home/home_2.dart
import '/features/cats/cats_screen.dart';
import '/features/favourite/favourite.dart';
import '/features/profile/profile.dart';
import 'package:flutter/material.dart';
class HomeScreenUser extends StatefulWidget {
HomeScreenUser({Key? key}) : super(key: key);
@override
_HomeScreenUserState createState() => _HomeScreenUserState();
}
class _HomeScreenUserState extends State<HomeScreenUser> {
int _currentIndex = 0;
PageController _pageController = PageController();
List<Widget> _screens = [
CatsScreen(),
Favorites(),
UserProfile(),
];
void _onPageChanged(int index) {}
void onTapped(int _currentIndex) {
_pageController.jumpToPage(_currentIndex);
}
@override
Widget build(BuildContext context) => DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: true,
title: Text('Cats App'),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[Colors.blue, Colors.purple])),
),
),
body: PageView(
controller: _pageController,
children: _screens,
onPageChanged: _onPageChanged,
),
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.blue,
onTap: (index) {
setState(() {
_currentIndex = index;
onTapped(_currentIndex);
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add_a_photo),
label: "Cats",
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: "Favourite",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Profile",
),
],
unselectedItemColor: Colors.blue,
),
),
);
}
lib/features/profile/profile.dart
import 'package:cached_network_image/cached_network_image.dart';
import '/bloc/bloc_authentification/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_signin_button/button_builder.dart';
class UserProfile extends StatelessWidget {
const UserProfile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final _authBloc = BlocProvider.of<AuthBloc>(context);
return Scaffold(
body: Center(child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
height: 80,
),
Container(
width: 250,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
'${state.currentUser?.photoURL}'),
),
borderRadius: BorderRadius.circular(180),
color: Colors.white38),
),
SizedBox(
height: 30,
),
Text(
'${state.currentUser?.displayName}',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.blue),
),
SizedBox(
height: 100,
),
SignInButtonBuilder(
text: 'Log Out',
icon: Icons.logout,
onPressed: () {
_authBloc.add(LogOutEvent());
},
backgroundColor: Colors.blue,
)
],
);
},
)),
);
}
}
lib/utils/auth.dart
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final _auth = FirebaseAuth.instance;
User? get user => _auth.currentUser;
StreamSubscription<User?> get currentUser =>
_auth.authStateChanges().listen((User? user) => user);
Future<UserCredential> signInWithCredentials(AuthCredential credential) =>
_auth.signInWithCredential(credential);
Future<void> logout() => _auth.signOut();
}
Output
GitHub Repository
Source Code and Assets: Flutter Cats App.