A new Flutter project. https://github.com/RivaanRanawat/flutter-reddit-clone
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
For help getting started with Flutter development, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.
login_screen.dart
import 'package:flutter/material.dart';
import 'package:reddit_tutorial/core/common/sign_in_button.dart';
import '../../../core/constants/constants.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Image.asset(
Constants.logoPath,
height: 40,
),
actions: [TextButton(onPressed: () {}, child: const Text('Skip'))],
),
body: Column(
children: [
const SizedBox(height: 30),
const Text(
'Dive into anything',
style: TextStyle(
fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 0.5),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
Constants.logoEmotePath,
height: 300,
),
),
const SizedBox(height: 30),
const SignInButton(),
],
),
);
}
}
sign_in_button.dart
import 'package:flutter/material.dart';
import 'package:reddit_tutorial/core/constants/constants.dart';
import 'package:reddit_tutorial/theme/pallete.dart';
class SignInButton extends StatelessWidget {
const SignInButton({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton.icon(
onPressed: () {},
icon: Image.asset(
Constants.googlePath,
width: 35,
),
label: const Text(
'Continue with Google',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
backgroundColor: Pallete.greyColor,
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
)),
);
}
}
pubspec.yaml
firebase_core: ^2.9.0
firebase_storage: ^11.1.0
cloud_firestore: ^4.5.0
firebase_auth: ^4.4.0
google_sign_in: ^6.1.0
flutter_riverpod: ^2.3.4
android/app/google-services.json
{
"project_info": { // START: FlutterFire Configuration
classpath 'com.google.gms:google-services:4.3.10'
// END: FlutterFire Configuration
android/build.gradle
// START: FlutterFire Configuration
classpath 'com.google.gms:google-services:4.3.10'
// END: FlutterFire Configuration
lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
macos/Flutter/GeneratedPluginRegistrant.swift
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
}
pod install
lib/main.dart
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: MyApp()));
}
lib/features/auth/repository/auth_repository.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:reddit_tutorial/core/providers/firebase_providers.dart';
final authRepositoryProvider = Provider((ref) => AuthRepository(
firestore: ref.read(firestoreProvider),
auth: ref.read(authProvider),
googleSignIn: ref.read(googleSignInProvider)));
class AuthRepository {
final FirebaseFirestore _firebase;
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
AuthRepository({
required FirebaseFirestore firestore,
required FirebaseAuth auth,
required GoogleSignIn googleSignIn,
}) : _auth = auth,
_firebase = firestore,
_googleSignIn = googleSignIn;
void signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final googleAuth = await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
UserCredential userCredential =
await _auth.signInWithCredential(credential);
print(userCredential.user?.email);
} catch (e) {
print(e);
}
}
}
lib/features/auth/controller/auth_controller.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../repository/auth_repository.dart';
final authControllerProvider = Provider((ref) => AuthController(authRepository: ref.read(authRepositoryProvider)));
class AuthController {
final AuthRepository _authRepository;
AuthController({required AuthRepository authRepository})
: _authRepository = authRepository;
void signInWithGoogle() {
_authRepository.signInWithGoogle();
}
}
lib/core/providers/firebase_providers.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_sign_in/google_sign_in.dart';
final firestoreProvider = Provider((ref) => FirebaseFirestore.instance);
final authProvider = Provider((ref) => FirebaseAuth.instance);
final storageProvider = Provider((ref) => FirebaseStorage.instance);
final googleSignInProvider = Provider((ref) => GoogleSignIn());
lib/core/common/sign_in_button.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reddit_tutorial/core/constants/constants.dart';
import 'package:reddit_tutorial/features/auth/controller/auth_controller.dart';
import 'package:reddit_tutorial/theme/pallete.dart';
class SignInButton extends ConsumerWidget {
const SignInButton({super.key});
void signInWithGoogle(WidgetRef ref) {
ref.read(authControllerProvider).signInWithGoogle();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton.icon(
onPressed: () => signInWithGoogle(ref),
icon: Image.asset(
Constants.googlePath,
width: 35,
),
label: const Text(
'Continue with Google',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
backgroundColor: Pallete.greyColor,
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
)),
);
}
}
firebase_constants.dart
class FirebaseConstants {
static const usersCollection = 'users';
static const communitiesCollection = 'communities';
static const postsCollection = 'posts';
static const commentsCollection = 'comments';
}
constants.dart
static const bannerDefault =
'https://thumbs.dreamstime.com/b/abstract-stained-pattern-rectangle-background-blue-sky-over-fiery-red-orange-color-modern-painting-art-watercolor-effe-texture-123047399.jpg';
static const avatarDefault =
'https://external-preview.redd.it/5kh5OreeLd85QsqYO1Xz_4XSLYwZntfjqou-8fyBFoE.png?auto=webp&s=dbdabd04c399ce9c761ff899f5d38656d1de87c2';
}
user_model.dart
import 'package:flutter/foundation.dart';
class UserModel {
final String name;
final String profilePic;
final String banner;
final String uid;
final bool isAuthenticated; // if guest or not
final int karma;
final List<String> awards;
UserModel({
required this.name,
required this.profilePic,
required this.banner,
required this.uid,
required this.isAuthenticated,
required this.karma,
required this.awards,
});
UserModel copyWith({
String? name,
String? profilePic,
String? banner,
String? uid,
bool? isAuthenticated,
int? karma,
List<String>? awards,
}) {
return UserModel(
name: name ?? this.name,
profilePic: profilePic ?? this.profilePic,
banner: banner ?? this.banner,
uid: uid ?? this.uid,
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
karma: karma ?? this.karma,
awards: awards ?? this.awards,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'profilePic': profilePic,
'banner': banner,
'uid': uid,
'isAuthenticated': isAuthenticated,
'karma': karma,
'awards': awards,
};
}
factory UserModel.fromMap(Map<String, dynamic> map) {
return UserModel(
name: map['name'] as String,
profilePic: map['profilePic'] as String,
banner: map['banner'] as String,
uid: map['uid'] as String,
isAuthenticated: map['isAuthenticated'] as bool,
karma: map['karma'] as int,
awards: List<String>.from(
(map['awards'] as List<String>),
));
}
@override
String toString() {
return 'UserModel(name: $name, profilePic: $profilePic, banner: $banner, uid: $uid, isAuthenticated: $isAuthenticated, karma: $karma, awards: $awards)';
}
@override
bool operator ==(covariant UserModel other) {
if (identical(this, other)) return true;
return other.name == name &&
other.profilePic == profilePic &&
other.banner == banner &&
other.uid == uid &&
other.isAuthenticated == isAuthenticated &&
other.karma == karma &&
listEquals(other.awards, awards);
}
@override
int get hashCode {
return name.hashCode ^
profilePic.hashCode ^
banner.hashCode ^
uid.hashCode ^
isAuthenticated.hashCode ^
karma.hashCode ^
awards.hashCode;
}
}
auth_repository.dart
final authRepositoryProvider = Provider((ref) => AuthRepository(
firestore: ref.read(firestoreProvider),
auth: ref.read(authProvider),
googleSignIn: ref.read(googleSignInProvider)));
class AuthRepository {
final FirebaseFirestore _firebase;
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
AuthRepository({
required FirebaseFirestore firestore,
required FirebaseAuth auth,
required GoogleSignIn googleSignIn,
}) : _auth = auth,
_firebase = firestore,
_googleSignIn = googleSignIn;
CollectionReference get _user => _firebase.collection(FirebaseConstants.usersCollection);
void signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final googleAuth = await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
UserCredential userCredential =
await _auth.signInWithCredential(credential);
UserModel userModel = UserModel(
name: userCredential.user!.displayName ?? 'No Name',
profilePic: userCredential.user!.photoURL ?? Constants.avatarDefault,
banner: Constants.bannerDefault,
uid: userCredential.user!.uid,
isAuthenticated: true,
karma: 0,
awards: []);
await _user.doc(userCredential.user!.uid).set(userModel.toMap());
} catch (e) {
print(e);
}
}
}
pubspec.yaml
fpdart: ^0.5.0
lib/core/failure.dart
class Failure {
final String message;
Failure(this.message);
}
lib/core/type_defs.dart
import 'package:fpdart/fpdart.dart';
import 'failure.dart';
typedef FutureEither<T> = Future<Either<Failure, T>>;
typedef FutureVoid = FutureEither<void>;
```dart
import 'package:flutter/material.dart';
void showSnackBar(BuildContext context, String text) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(text),
),
);
}
lib/core/utils.dart
import 'package:flutter/material.dart';
void showSnackBar(BuildContext context, String text) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(text),
),
);
}
lib/features/auth/controller/auth_controller.dart
final authControllerProvider = Provider(
(ref) => AuthController(authRepository: ref.read(authRepositoryProvider)));
void signInWithGoogle(BuildContext context) async {
final user = await _authRepository.signInWithGoogle();
user.fold((l) => showSnackBar(context, l.message), (r) => null);
}
lib/features/auth/repository/auth_repository.dart
final authRepositoryProvider = Provider((ref) => AuthRepository(
firestore: ref.read(firestoreProvider),
auth: ref.read(authProvider),
googleSignIn: ref.read(googleSignInProvider)));
class AuthRepository {
final FirebaseFirestore _firebase;
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
AuthRepository({
required FirebaseFirestore firestore,
required FirebaseAuth auth,
required GoogleSignIn googleSignIn,
}) : _auth = auth,
_firebase = firestore,
_googleSignIn = googleSignIn;
CollectionReference get _users =>
_firebase.collection(FirebaseConstants.usersCollection);
FutureEither<UserModel> signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final googleAuth = await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
UserCredential userCredential =
await _auth.signInWithCredential(credential);
UserModel userModel;
if (userCredential.additionalUserInfo!.isNewUser) {
userModel = UserModel(
name: userCredential.user!.displayName ?? 'No Name',
profilePic:
userCredential.user!.photoURL ?? Constants.avatarDefault,
banner: Constants.bannerDefault,
uid: userCredential.user!.uid,
isAuthenticated: true,
karma: 0,
awards: []);
await _users.doc(userCredential.user!.uid).set(userModel.toMap());
} else {
userModel = await getUserData(userCredential.user!.uid).first;
}
return right(userModel);
} on FirebaseException catch (e) {
throw e.message!;
} catch (e) {
return left(Failure(e.toString()));
}
}
lib/core/common/loader.dart
import 'package:flutter/material.dart';
class Loader extends StatelessWidget {
const Loader({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}
lib/core/common/sign_in_button.dart
void signInWithGoogle(BuildContext context, WidgetRef ref) {
ref.read(authControllerProvider.notifier).signInWithGoogle(context);
}
lib/features/auth/controller/auth_controller.dart
final userProvider = StateProvider<UserModel?>((ref) => null);
final authControllerProvider = StateNotifierProvider<AuthController, bool>(
(ref) => AuthController(
authRepository: ref.watch(authRepositoryProvider), ref: ref));
class AuthController extends StateNotifier<bool> {
final AuthRepository _authRepository;
final Ref _ref;
AuthController({required AuthRepository authRepository, required Ref ref})
: _authRepository = authRepository,
_ref = ref,
super(false); //loading
void signInWithGoogle(BuildContext context) async {
state = true;
final user = await _authRepository.signInWithGoogle();
state = false;
user.fold(
(l) => showSnackBar(context, l.message),
(userModel) =>
_ref.read(userProvider.notifier).update((state) => userModel),
);
}
}
lib/features/auth/screens/login_screen.dart
class LoginScreen extends ConsumerWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = ref.watch(authControllerProvider);
return Scaffold(
appBar: AppBar(
title: Image.asset(
Constants.logoPath,
pubspec.yaml
routemaster: ^1.0.1
lib/router.dart
import 'package:flutter/material.dart';
import 'package:reddit_tutorial/features/auth/screens/login_screen.dart';
import 'package:routemaster/routemaster.dart';
final loggedOutRoute = RouteMap(routes: {
'/': (_) => const MaterialPage(child: LoginScreen())
});
lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Reddit Tutorial',
theme: Pallete.darkModeAppTheme,
routerDelegate: RoutemasterDelegate(routesBuilder: (context) => loggedOutRoute),
routeInformationParser: const RoutemasterParser(),
);
}
}
lib/core/common/error_text.dart
import 'package:flutter/material.dart';
class ErrorText extends StatelessWidget {
final String error;
const ErrorText({super.key, required this.error});
@override
Widget build(BuildContext context) {
return Center(child: Text(error),);
}
}
lib/features/auth/controller/auth_controller.dart
final userProvider = StateProvider<UserModel?>((ref) => null);
final authControllerProvider = StateNotifierProvider<AuthController, bool>(
(ref) => AuthController(
authRepository: ref.watch(authRepositoryProvider), ref: ref));
final authStateChangeProvider = StreamProvider((ref) {
final authController = ref.watch(authControllerProvider.notifier);
return authController.authStateChange;
});
final getUserDataProvider = StreamProvider.family((ref, String uid) {
final authController = ref.watch(authControllerProvider.notifier);
return authController.getUserData(uid);
});
class AuthController extends StateNotifier<bool> {
final AuthRepository _authRepository;
final Ref _ref;
AuthController({required AuthRepository authRepository, required Ref ref})
: _authRepository = authRepository,
_ref = ref,
super(false); //loading
Stream<User?> get authStateChange => _authRepository.authStateChange;
void signInWithGoogle(BuildContext context) async {
state = true;
final user = await _authRepository.signInWithGoogle();
state = false;
user.fold(
(l) => showSnackBar(context, l.message),
(userModel) =>
_ref.read(userProvider.notifier).update((state) => userModel),
);
}
Stream<UserModel> getUserData(String uid) {
return _authRepository.getUserData(uid);
}
}
lib/features/auth/repository/auth_repository.dart
Stream<User?> get authStateChange => _auth.authStateChanges();
lib/features/home/screen/home_screen.dart
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider)!;
return Scaffold(
body: Center(child: Text(user.name)),
);
}
}
lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
UserModel? userModel;
void getData(WidgetRef ref, User data) async {
userModel = await ref
.watch(authControllerProvider.notifier)
.getUserData(data.uid)
.first;
ref.read(userProvider.notifier).update((state) => userModel);
setState(() {});
}
@override
Widget build(BuildContext context) {
return ref.watch(authStateChangeProvider).when(
data: (data) => MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Reddit Tutorial',
theme: Pallete.darkModeAppTheme,
routerDelegate: RoutemasterDelegate(routesBuilder: (context) {
if (data != null) {
getData(ref, data);
if (userModel != null) {
return loggedInRoute;
}
}
return loggedOutRoute;
}),
routeInformationParser: const RoutemasterParser(),
),
error: (error, stackTrace) => ErrorText(error: error.toString()),
loading: () => const Loader(),
);
}
}
lib/router.dart
final loggedInRoute = RouteMap(routes: {
'/': (_) => const MaterialPage(child: HomeScreen()),
});