This repo contains the source code for my Flutter & Firebase Course on Udemy.
To use this project with Firebase, some configuration steps are required.
- Create a new project with the Firebase console.
- Add iOS and Android apps in the Firebase project settings.
- On Android, use
com.codingwithflutter.time_tracker_flutter_course
as the package name. - then, download and copy
google-services.json
intoandroid/app
. - On iOS, use
com.codingwithflutter.timeTrackerFlutterCourse
as the bundle ID. - then, download and copy
GoogleService-Info.plist
intoiOS/Runner
, and add it to the Runner target in Xcode.
See this page for full instructions:
The setup steps are also explained in the course.
All diagrams shown in the course are available at this URL:
- Course Introduction
- Course Content
- App Overview
- Making the most of this course
- What is Flutter
- The Dart Language
- Introduction to Dartpad
- A simple program
- Variable declaration and initialization
- String interpolation
- Type inference with var
- Var and final
- The dynamic keyword
- Introduction to functions
- Function return types
- Optional parameters, nullability and default values
- Named parameters
- The arrow operator
- Introduction to classes
- Class constructors
- Instance methods
- Inheritance
- The super constructor
- The base Object class and the toString method
- Overriding the toString method
- Abstract classes
- More on abstract classes
- Using abstract classes with functions
- Computed properties
- Mixins
- Introduction to lists
- Introduction to maps
- Generics and type annotations
- If and else statements
- The ternary operator
- The while loop
- The for loop
- Closures and the fold method
- Enumerations
- Switch statements
- Wrap-up
- Flutter setup on macOS
- Setting the PATH variable
- Flutter doctor
- Xcode and iOS simulator setup
- Installing Android Studio
- Installing the Android emulator
- Running Flutter from the command line
- Flutter setup on Android Studio
- Installing Visual Studio Code
- Flutter setup on Windows
- Updating the path variable
- Flutter doctor
- Installing Android Studio
- Installing the Android emulator
- Running Flutter from the command line
- Flutter setup on Android Studio
- Installing Visual Studio Code
- Creating a Flutter project with Android Studio
- A tour of the project folders
- Running the Android emulator and iOS simulator
- Overview of the Flutter counter app
- Hot reload and hot restart
- Introduction to widgets
- The MaterialApp widget
- The Scaffold widget
- The Flutter widget tree
- Stateless and stateful widgets
- Updating the counter with setState
- Wrap up
- Overview of the Time tracker app
- Switching between apps
- Writing the root widget of the app
- Adding the MaterialApp
- Adding some folders to our project
- Adding a sign-in page
- The ThemeData class
- The AppBar widget
- Preview of the SignInPage layout
- Adding a Column layout
- The CrossAxisAlignment property
- Code formatting with dartfmt
- Adding some boxes and extracting code into a method
- Private methods
- Adding some padding
- The MainAxisAlignment property
- Text, TextStyle and FontWeight
- Introduction to buttons
- Adding the first button
- Button callbacks explained
- Customising button colors
- MaterialColor explained
- Changing button shapes
- Making code reusable
- Creating a reusable custom RaisedButton
- Creating a reusable SignInButton
- Setting default values
- Making the button height configurable
- Adding the remaining buttons
- Adding logos: introduction
- Updating the pubspec.yaml file
- Image variants
- Adding an image inside a button
- Arranging widgets horizontally in a Row
- The Opacity widget
- Creating a custom SocialSignInButton
- The @required annotation
- Using assertions for better widget API design
- Local and remote authentication
- Introduction to Firebase
- Creating a Firebase project
- Configuring Firebase for Android
- Configuring Firebase for iOS
- Installing the firebase_core and firebase_auth packages
- Initializing the Firebase App
- Running on iOS and updating Cocoapods
- Futures, async and await
- Signing in anonymously with Firebase
- The FirebaseAuth singleton and private constructors
- Explaining the short-hand syntax for callbacks
- Error handling with try/catch
- Preview of the sign-in and sign-out flow
- Creating a landing page widget
- Adding a Firebase User to the LandingPage
- Adding a callback to the SignInPage
- Hooking up the onSignIn callback
- Creating the home page
- Adding the sign-out functionality
- Hooking up the onSignOut callback
- Retrieving the current user when the app starts
- Explaining global access and scoped access
- Creating the Auth class
- The abstract AuthBase class
- Using the Auth class
- Lifting state up and its drawbacks
- State Management & App Architecture
- Introduction to Streams
- Streams in practice with DartPad
- Handling errors and closing streams
- The authStateChanges stream
- Listening to the authStateChanges stream
- Adding the StreamBuilder code
- More on StreamBuilder
- Refactoring the sign-in flows
- Wrap-up on Streams and StreamBuilder
- Overview of the Firebase sign-in methods
- Enabling support for Google Sign In
- Adding Google Sign-In to the Auth class
- Hooking up Google Sign-In to our button
- Configuring Google Sign-In on iOS
- Google Sign-In flow explained
- Supporting Google Sign Out
- Testing Google Sign-In on Android
- Viewing registered users on the Firebase console
- Registering a Facebook App
- Enabling Facebook Sign-In on Firebase
- Installing the Facebook login package
- Enabling MultiDex support on Android
- Adding the Facebook Sign-In code
- Testing Facebook Sign-In on Android
- Facebook iOS setup in Xcode
- Testing Facebook Sign-In on iOS
- Accessing the user's data and privacy considerations
- Preview of the email & password sign-in page
- Creating the email & password sign-in page
- Passing the BuildContext across methods
- Introduction to navigation
- Adding a Card widget
- Adding the email and password text fields
- Adding the submit buttons
- Creating a FormSubmitButton widget
- Adding a TextEditingController
- Toggling the form type
- Adding the email & password authentication code
- Implementing the submit method
- Testing email & password sign-in
- Customising the email and password text fields
- Using FocusNode and FocusScope
- Disabling the submit button on empty email or password
- Adding a StringValidator class
- Adding an email and password validation mixin
- Showing an error text when the email or password are invalid
- Tweaking form submission
- Simulating a slow network with a delay
- Adding a loading state to our form
- Updating the email focus logic
- Fixing the vertical overflow on small screens
- Wrap-up
- Introduction to dialogs
- Showing a dialog
- Dismissing dialogs
- Platform-aware widgets on iOS, Android & more
- Adding a reusable showAlertDialog function
- Adding a sign-out confirmation dialog
- Dialog differences on Android and iOS
- Introduction to InheritedWidget
- Creating an AuthProvider
- Accessing the Auth object via the AuthProvider
- Adding the provider package
- Using the Provider class
- Wrap-up about scoped access and Provider
- Module Introduction
- Creating better user-facing errors with FirebaseAuthException
- Creating a custom exception alert dialog
- Showing error alerts in the SignInPage
- Adding a loading state: overview
- Adding a loading state to the SignInPage
- Using the loading state in the SignInPage
- The dispose method
- Introduction to state management with BLoCs
- The application layers
- BLoCs, sinks, streams, and asynchronous code
- Introduction to the SignInBloc
- Implementing a simple BLoC
- Adding a Bloc with Provider inside a static method
- Adding the StreamBuilder code
- Converting the SignInPage to a stateless widget
- The difference between Provider.of and Consumer
- Disposing BLoCs with Provider
- Adding authentication code to the SignInBloc
- Updating the SignInPage
- Fixing the BLoC submit method
- Summary on the BLoC basics
- Introduction to the email sign-in flow with BLoC
- Creating a model class for the EmailSignInForm
- Creating the EmailSignInBloc with a StreamController
- Updating the model
- Adding the BLoC submit method
- Setting up the EmailSignInFormBlocBased with Provider
- Refactoring the EmailSignInFormBlocBased widget by removing the state variables
- Moving the business logic to the BLoC class
- Moving more business logic to the model class
- The benefits of separation of concerns with BLoC
- Using stateful widgets with TextEditingControllers
- Considerations about performance
- Blocs and Services in the widget tree
- Recap on State Management
- Introduction to ValueNotifier
- Adding a ValueNotifier with ChangeNotifierProvider
- Consumer and ChangeNotifierProvider explained
- Differences between BLoC/streams and ValueNotifier/ChangeNotifierProvider
- Introduction to ChangeNotifier
- Adding the EmailSignInChangeModel class
- Completing the EmailSignInChangeModel class
- Implementing the email sign-in form with ChangeNotifier
- Comparing ValueNotifier and ChangeNotifier
- Wrap up on State Management
- Wrap up on the Authentication Flows
- Overview of the time tracker app
- Database schema and SQL vs NoSQL
- Introduction to Cloud Firestore
- Documents and Collections
- Getting started with Firestore
- Designing a Database API with CRUD operations
- Managing private user data with Cloud Firestore
- Installing Cloud Firestore
- Renaming the HomePage to JobsPage
- Adding the Database class
- Adding the Database Provider
- Adding a FloatingActionButton
- Writing data to Firestore
- Defining a strongly-typed Job model class
- Defining a common API path class
- Adding a generic setData method
- Adding security rules
- Handling Firestore permissions errors
- Reading data from Firestore
- Reading and parsing Firestore data streams
- Adding a StreamBuilder to show a list of jobs
- Debugging the StreamBuilder code
- Firestore as a realtime database
- Adding a factory constructor to our model class
- Adding a generic method to read Firestore streams
- Adding a FirestoreService class
- Wrap-up on Cloud Firestore
- Introduction to Forms with Cloud Firestore
- Adding a new job page
- The Placeholder widget
- Introduction to Form and TextFormField
- Validating and saving Form data
- Accessing the Database object with the correct BuildContext
- Saving jobs with a unique document ID
- Handling errors
- Enforcing unique job names
- Fixing the integer-parsing code
- Editing existing jobs: overview
- Adding a custom JobListTile
- Repurposing the AddJobPage for editing jobs
- Reading the documentID from Firestore
- Completing the code for editing jobs
- Wrap up on working with Forms
- Intro and multiple states of UI
- Adding an empty content widget
- Adding a reusable list items builder
- Using ListView.builder
- Using ListView.separated
- Deleting jobs from Firestore
- Adding swipe to delete support
- Working with entries: overview
- Relational data & drawbacks of NoSQL databases
- Getting ready to add new files
- Adding the source files to the project
- Connecting the new code and updating the Firestore rules
- Fixing the EditJobPage navigation
- Overview of the JobEntriesPage
- Reading and writing entries with Firestore
- The EntryListItem widget (using InkWell and Expanded)
- Formatting dates and currencies with the Intl package
- Dart as UI: Spreads and Collection-if
- Using date pickers with stateful widgets
- Date and time input with a custom UI and DateTimePicker
- Updating the UI when a Job changes
- Wrap up and CupertinoDatePicker
- Introduction to bottom navigation
- Multiple navigation stacks
- Creating a HomePage with a selected tab
- Adding a CupertinoTabScaffold
- Testing the bottom navigation
- Adding the widget builders
- Replacing the FloatingActionButtons
- Moving the logout button to the AccountPage
- Presenting modal routes with the root navigator
- The CupertinoPageRoute
- Handling the Android back button with WillPopScope and navigator keys
- Adding pop-to-root navigation
- Wrap up on multiple navigators
- Introduction to advanced stream operations
- Introduction to RxDart
- Observable.combineLatest and data transformations in the time tracker app
- Adding the source code for the new entries page
- Reviewing the UI code for the entries page
- Using combineLatest in practice
- Data manipulation in the EntriesBloc
- Wrap up on Observables
- Single subscription vs broadcast streams
- PublishSubject, ReplaySubject, BehaviorSubject
- Adding a BehaviorSubject to the EmailSignInBloc
- Wrap up and notes about local and remote state management
- Completing the time tracker app: overview
- Accessing the User object in the AccountPage
- Adding an Avatar image
- Finishing the Avatar code
- Wrapping up the time tracker app
- Introduction to writing tests
- Testing Flutter Apps
- Writing the first unit test
- Running tests
- Checking and fixing errors in tests
- Testing edge cases by writing and fixing failing tests
- Grouping tests together
- The setUp method and testing date formatting with locales
- The test lifecycle methods
- Completing the formatting tests
- Testing model classes
- hashCode and the == operator
- Adding a toString() method, wrap up on unit tests
- Introduction to widget tests
- Finding widgets and matcher arguments
- Testing widget callbacks
- Working with Acceptance Criteria
- Introduction to test mocks and mockito
- Injecting mock objects with Provider
- Verifying mock methods
- Working with keys, entering text and the pump() method
- Testing widget updates on state changes
- Completing the email sign-in tests
- Replacing Navigator.pop with a callback when the user signs in
- Updating the tests to handle the form callback
- Stubbing mock objects
- Recap on the email sign in forms and stubbing mocks
- Using widget tests with StreamBuilder
- Using StreamController inside tests
- Adding a Database builder to the Landing Page
- Test setup for the SignInPage
- Adding keys to custom widget classes
- Testing navigation
- The great thing about widget tests
- Testing ValueNotifier models
- Testing ChangeNotifier models
- Testing BloCs
- Comparing EmailSignInModel objects
- Testing streams in Blocs
- Wrap up on unit & widget tests
- Conclusion and Next Steps