GithubHelp home page GithubHelp logo

theamazingbnd / startupweekdemo Goto Github PK

View Code? Open in Web Editor NEW
4.0 1.0 1.0 12 MB

A demo to show how to build your first android app and link it with a Firebase backend.

Kotlin 67.83% Ruby 0.47% Swift 31.70%

startupweekdemo's Introduction

Description

In honor of Detriots startup week we have taking the liberty of implementing a basic app, that aspiring engineers and entrepreneur can use as a source of knowledge and inspiration for their own projects. This project has been accommodated to run on both the Android and iOS platforms, while maintaining a shared custom backend implented via Google Firebase.

The project we have decided to create is a simple reminder application. Throughout the course of this README we will be explaining how to: implement email and password authentication using Firebase/Auth, reading and writing to a NoSQL database using Firebase/Database, and how to construct relationships between models in JSON.

Click here for Android

Click here for IOS

iOS

Contents

  1. Creating A New XCode Project
  2. Setting Up Firebase
  3. Installing Firebase Using CocoaPods
  4. Authenticating Users With Firebase
  5. Interacting With The Firebase Database
  6. Conclusion

If you don't already have Xcode installed on your computer you can install directly from the Mac AppStore. Once you open the application you will be promted with the following screen.

Screen Shot 2019-06-15 at 8 39 48 PM

Go ahead and click Create a new Xcode project and select the Single view app is selected under the Application section. You will then be prompted to name your application, and perhaps for an organization name and/or identifer. You can use anything in pretty anything you want for these fields. Tap next and congratulations you just created an Xcode project!

We will want to create a Firbase project before we get too ahead of ourselves. Head over to https://firebase.google.com/ and in the top right hand corner click on the Go to console button (You will need to be signed into a google account to access the console). From here we can tap Create a project, enter in a name for the project, accept and agree to the controller-controller and applicable terms and tap Create project.

We will now to add an iOS app to our project. In the project overview section tap the iOS button; here you will be prompted for a bundle identifier. Open xcode back up and in the Project Navigator (Folder icon in the top left hand corner) and tap the blue project icon. This will expose you Xcode projects bundle identifier, copy it and paste it in the iOS bundle ID field in Firebase and tap Register App.

Next download the GoogleService-Info.plist and drag and drop it into the Xcode project underneath the current Info.plist file. Congratulations you just successfully created a Firebase project and registered your iOS app!

You may be asking yourself what is CocoaPods? CocoaPods is a dependency manager for Swift and Objective-C projects. A dependency manager is a tool that allows to import external software libraries into your project; if your a curious to learn more about what CocoaPods is see here.

To install CocoaPods open Terminal and type the following command:

sudo gem install cocoapods

If you are having trouble installing CocoaPods see here for potential troubleshooting solutions.

Now to add Firebase to our project, while still in Terminal navigate to your Xcode project's directory using the cd command here is an example of how to do this. Say your project is name StarterProject and you placed it on your Desktop to navigate into the directory you use the following commands:

cd ~
cd Desktop
cd StarterProject

once there run pod init. This will create a Podfile for us to integrate the libraries whaat we need. Open up the Podfile which should now be in the project folder and add the following snippet under the # Pods for ..... line.

  pod 'Firebase'
  pod 'Firebase/Auth'
  pod 'Firebase/Database'

Save the file and back in Terminal run pod install, this will generate a .xcworkspace file for us. Close the current instance of Xcode if you haven't already and reopen Xcode from the .xcworkspace file. This is were we will be creating and editing our project.

Now we will need to configure a FirebaseApp shared instance. Open up AppDelegate.swift and update the code to look like so:

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FirebaseApp.configure()
        return true
    }

You're now ready to code!

The first thing we will need to do is create a User model to do so create a new file name User.swift and add the following code to the file.

struct User: Equatable {
    var uid: String?
    var email: String?
    var firstName: String?
    var lastName: String?
}

If we wanted to we could hold a reference to the user that will persist inbetween app launches like so

extension User {
    func saveToDefaults() {
        UserDefaults.standard.set(self.uid, forKey: "me")
    }
}

The code below is a wrapper used to create a new user. In this project this codes lives in AuthenticationManager.swift which is a Singleton that handles/manage all functions and members related to authentication.

    func signUp(with email: String?, password: String?, firstName: String, lastName: String, completion: @escaping (User?, Error?) -> ()) {
        /// unwrap email and password
        guard let email = email,
            let password = password else {
                return
        }
        
        /// Create a new Firebase user using email and password
        Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
            if let err = error {
                print("Error Signing In: \(err.localizedDescription)")
                completion(nil, err)
                return
            }
            
            guard let result = result else {
                print("Error Signing In No User Data")
                completion(nil, nil)
                return
            }
            
            let user = User(uid: result.user.uid, email: email, firstName: firstName, lastName: lastName)
                       
            /// save reference of user to UserDefaults           
            user?.saveToDefaults()
            
            /// set shared instance user to newly created user
            self?.user = user
            completion(user, nil)
        }
    }

The snippet above is a function that takes in 4 strings, one for an email, password, first name, and last name. It also takes in a completion handler.

So if we have a valid email, and password we use the Firebase Auth.auth().createUser(withEmail:,password:,completion:) method, within the callback of the createUser method we check to see if an error was return. If there was an error we pass it along to our completion handler. Otherwise we unwrap the result, create a user with that result and pass the user to our completion handler instead.

Logging in with an existing user is pretty similar to creating a new user. The code below is a wrapper for logging in an existing user

func login(with email: String?, password: String?, completion: @escaping (User?, Error?) -> ()) {
        /// unwrap email and password
        guard let email = email,
            let password = password else {
                return
        }
        
        /// Sign in with an existing Firebase user using email and password
        Auth.auth().signIn(withEmail: email, password: password) { [weak self] result, error in
            if let err = error {
                print("Error Signing In: \(err.localizedDescription)")
                completion(nil, err)
                return
            }
            
            guard let result = result else {
                print("Error Signing In No User Data")
                completion(nil, nil)
                return
            }
            
            /// Fetch the user data that is save in the database
            DatabaseManager.shared.fetchUser(with: result.user.uid, completion: { [weak self] user, error in
                if let err = error {
                    completion(nil, err)
                    return
                }
                
                /// save reference of user to UserDefaults
                user?.saveToDefaults()
                
                /// set shared instance user to newly created user
                self?.user = user
                completion(user, nil)
            })
        }
    }

As you can see the format is exactly the same the only difference here is that instead of sending up first and last name data we must now fetch that data. We can do so by using the DatabaseManager.fetchUser(with:completion:)

All methods and members relating to the database are on the DatabaseManager Singleton class. We will need to key into specific nodes of our database to extract specific data so we will create an enum that looks like so

enum Nodes: String {
    case users = "Users"
    case reminders = "Reminders"
}

we will be using this enum to key into our uppermost level nodes.

Now we need to set up the database in our Firebase console. In the side menu on the left select the Database option. There are two database options in Firebase Cloud Firestore and Realtime Database, in this project we are using the Realtime Database so make sure when you create your database that you are using the Realtime Database option. I would also recommend to choose Start in test mode, but it's not mandataory since we are authenticating our users.

When creating a new user we will want write that user to our database. We can leverage the Firebase Database.database().setValue(_:) method to send up the necessary values. We can't simply send up our models we will need to map our User to a Dictionary<String, Any> so that we can properly write our user to the database. To do so we can create this helper on user

extension User {
    func toDictionary() -> [String : Any] {
        return ["email" : email ?? "",
                "firstName" : firstName ?? "",
                "lastName" : lastName ?? ""]
    }
}

Now that we have a way to convert our User model to a Dictionary<String, Any> we can now see how we will save our user to that database, but before we look at the code lets consider how we want the data to be structed in our database.

We will need a node to hold reference of where all of our users are stored. Next we will need a way to identify each individual user; we can use a unique identifer for this. And lastly we will need to save the user information. Base on this criteria our data should look like so:

Screen Shot 2019-06-16 at 6 08 03 PM

Now lets look at the code needed to write our user up to the database while maintaining the structure above

    func putUser(user: User?, completion: @escaping (Error?) -> ()) {
        guard let user = user,
            let uid = user.uid else {
            return
        }
        
        Database.database().reference().child(Nodes.users.rawValue).child(uid).setValue(user.toDictionary()) { (error, _) in
            completion(error)
        }
    }

This function calls into Database.database().reference() which is defined in the GoogleService-Info.plist file we imported earlier. Next we will key into the Users node by using .child(Nodes.users.rawValue) (Nodes.users.rawValue is the same as saying "Users"), followed by keying into the users unique id node using .child(uid) and set the value using our Dictionary<String, Any> representation. If an error is return then we will just pass it into our completion handler.

When reading in a value from the database using any of the FireBase Database.database().observe(of:with:) methods use a completion handler in which the callback returns a Firebase DataSnapshot, so we will need to a way to transform the Datasnapshot into our user model. We can do somthin similar to the following:

extension DataSnapshot {
    func toUser() -> User? {
        /// cast value to Dictionary<String, Any>
        guard let dict = value as? [String : Any] else {
            return nil
        }
        
        /// grab values from the dictionary
        let email = dict["email"] as? String
        let firstName = dict["firstName"] as? String
        let lastName = dict["lastName"] as? String
        
        /// create and return a new instance of User
        return User(uid: key,
                    email: email,
                    firstName: firstName,
                    lastName: lastName)
    }
}

Now that we have a way of transforming DataSnapshot into a User we can now take a look and see how we can leverage Database.database().observe(of:with:) and the newly add DataSnapshot.toUser() functions to fetch a user from the database.

    func fetchUser(with uid: String?, completion: @escaping (User?, Error?) -> ()) {
        guard let uid = uid else {
            completion(nil, nil)
            return
        }
        
        Database.database().reference().child(Nodes.users.rawValue).child(uid).observeSingleEvent(of: .value, with: { snapshot in
            completion(snapshot.toUser(), nil)
        }) { error in
            completion(nil, error)
        }
    }

This method takes in a string representing the users unique id and a completion handler. This function calls into Database.database().reference(). Next we will key into the Users node by using .child(Nodes.users.rawValue), followed by keying into the users unique id node using .child(uid) and use the Firebase Database.database().observeSingleEvent(of:with:withCancel:) method to read in the values for a user.

Before we can write a Reminder reminder we must first create the corresponding data type. Create a new file and name it Reminder.swift and add the following code to the new file.

struct Reminder: Equatable {
    var id: String
    var title: String?
    var description: String?
    var timestamp: TimeInterval
    var isComplete: Bool
}

You may be wondering what the timestamp and member is for. When we are fetching our data from Firebase it comes back asynchronously which essentially means that the data that comes back may not be in the same order as it was before, so we can use the timestamp member to sort the data based on the most recent reminder add

Now that we have our model setup we will need a way to cast this to a Dictionary<String, Any>. So we can use the following helper function to achieve this.

extension Reminder {
    func toDictionary() -> [String : Any] {
        return ["title" : title ?? "",
                "description" : description ?? "",
                "timestamp" : timestamp,
                "isComplete" : isComplete]
    }
}

Before we write the code for this lets take a step back and think about how we want this data to be structured in our database. We will have a top level node called "Reminders". Now each user will have their own list of reminders so we can create another node keyed by the user's unique identifier. Each reminder will need it's own node so we can achieve keying the node by the reminder's id, and finally we can set the reminders data. This should look like so

Screen Shot 2019-06-16 at 7 33 52 PM

Let's see how this will look in code

    func putReminder(reminder: Reminder?, title: String, description: String?, completion: @escaping (Error?) -> ()) {
        guard let uid = AuthenticationManager.shared.user?.uid else {
            return
        }
        
        let uuid = reminder?.id ?? UUID().uuidString
        let timestamp = reminder?.timestamp ?? Date().timeIntervalSince1970
        let isComplete = reminder?.isComplete ?? false
        let reminder = Reminder(id: uuid, title: title, description: description, timestamp: timestamp, isComplete: isComplete)
        
        Database.database().reference().child(Nodes.reminders.rawValue).child(uid).child(uuid).setValue(reminder.toDictionary()) { (error, reference) in
            completion(error)
        }
    }

This method takes in an optional Reminder, 2 strings, and a completion handler. We check to see if the current user is authenticated, then if the reminder passed in is not nil we can update the title, description, and isComplete members of the reminder. Otherwise we create a new uniquer identifier (UUID), set the timestamp to a the number of seconds that have passed since 1970, and set isComplete to false. We then key into the Reminders node of the database, next we key into the node corresponding to the current users uid, and then the node corresponding to the new reminders id; we then set the reminders values. If an error is return then we will just pass it into our completion handler.

Before we can read in reminders we must handle transforming a DataSnapshot into a Reminder this will differ from the way we pull in a User. The reason being is that we are going to key in the current users uid node that is nested inside the "Reminders" node, meaning that there are potentially more node inside (a.k.a children). But first we need to be able to transform a DataSnapshot into a Reminder

extension DataSnapshot {
    func toReminder() -> Reminder? {
        guard let dict = self.value as? [String : Any] else {
            return nil
        }
        
        let title = dict["title"] as? String
        let description = dict["description"] as? String
        let timestamp = dict["timestamp"] as? TimeInterval ?? Date().timeIntervalSince1970
        let isComplete = dict["isComplete"] as? Bool ?? false
        
        return Reminder(id: key,
                        title: title,
                        description: description,
                        timestamp: timestamp,
                        isComplete: isComplete)
    }
}

Now that we have our transformer set up, lets see how to read these in and parse these children from the database

    func fetchCurrentUserReminders(completion: @escaping ([Reminder]?, Error?) -> ()) {
        guard let uid = AuthenticationManager.shared.user?.uid else {
            return
        }
        
        Database.database().reference().child(Nodes.reminders.rawValue).child(uid).observeSingleEvent(of: .value, with: { snapshot in
            guard let children = snapshot.children.allObjects as? [DataSnapshot] else {
                return
            }
            
            var reminders = [Reminder]()
            
            for child in children {
                guard let reminder = child.toReminder() else {
                    continue
                }
                reminders.append(reminder)
            }
            
            reminders = reminders.sorted(by: { lhs, rhs -> Bool in
                return lhs.timestamp > rhs.timestamp
            })
            
            completion(reminders, nil)
        }) { error in
            completion(nil, error)
        }
    }

So first we check to see if the current user is authenicated. Then we key into the "Reminders" node of the database, followed by keying into node corresponding to the users uid. We can now leverage the Firebase Database.database().observeSingEvent(of:with:withCancel:) method to read in all the users reminders. So we cast our snapshot.children.allObjects to an array to an array of DataSnapshot. We can now iterate over that array and append each instance of transformed instance of Reminder to our Reminders array but before we can pass this into our completion handler we must sort the array base on timestamp we can do so by using the Array.sorted(by:) method. If an error is return then we will just pass it into our completion handler.

Android

Contents

  1. Creating A New Android Studio Project
  2. Setting Up Firebase
  3. Importing Firebase Into Our Project
  4. Authenticating Users With Firebase
  5. Interacting With The Firebase Database
  6. Conclusion

If you don't already have Android Studio installed on your computer you can install it from Here. Once you open the IDE you will be prompted with the following screen. This workshop will be a demo of MVVM architecture observing changes to data. We will be using Kotlin throughout this workshop. Android Apps can be made in Java but most companies including Google themselves are pushing away from Java.

Screen Shot 2019-06-15 at 8 39 48 PM

Go ahead and click File -> New ->New Project and select the Basic Activity. You will then be prompted to name your application, and perhaps for an organization name and/or identifier. Tap next and congratulations you just created an Android Studio project!

We will want to create a Firbase project before we get too ahead of ourselves. Head over to https://firebase.google.com/ and in the top right hand corner click on the Go to console button (You will need to be signed into a google account to access the console). From here we can tap Create a project, enter in a name for the project, accept and agree to the controller-controller and applicable terms and tap Create project.

We will now to add an Android app to our project. In the project overview section tap the Android button; here you will be prompted for a bundle identifier. Open Android Studio back up, head over to your App modules build.gradle. Here you will see an applicationId. Grab that and drop it into Firebase

Next download the google-services.json and drag and drop it into the Android project in App directory. Pop back over to the Firebase console and finish the prompted setup. Congratulations! You have just successfully created a Firebase project and registered your Android app!

Now to add Firebase to our dependencies. Add this snippet below to your projects build.gradle.

dependencies { 
 classpath 'com.google.gms:google-services:4.2.0' 
 …
 }

After this, add the snippet below to your App's build.gradle file.

dependencies { 
...
implementation 'com.google.firebase:firebase-core:17.0.0' 
implementation 'com.google.firebase:firebase-auth:18.0.0' 
implementation 'com.google.android.gms:play-services-auth:17.0.0' 
implementation 'com.google.firebase:firebase-database:18.0.0' 
 …
 }

For simplicity we will create some global instances of the Firebase DB and Firebase Auth. This will keep the amount of

val auth = FirebaseAuth.getInstance()
val db = FirebaseDatabase.getInstance()

You're now ready to code!

The first thing we will need to do is create a User Model to do so create a new kotlin file named User and add the following code to the file.

data class User ( 
val uid: String? = "",
val firstName: String? = "", 
val lastName: String? = "", 
val email: String? = "" 
)

If we wanted to we could hold a reference to the user that will persist between app launches we have to store this user.uid in Shared Preferences. Thank can be done by calling the snippet below. I separated this into it's own SharedPrefsManager class to make it easier to save this from other places in the app. For example the Login/Signup screens. The getString and putString methods are wrapped in functions.

Storing to Prefs:

private var prefs : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
 
prefs.getString(SharedPrefsKeys.USER_UID, "").orEmpty() 
prefs.edit().putString(SharedPrefsKeys.USER_UID, userId).apply()

This is what my manager class mentioned looks like.

SharedPrefsManager:

open class SharedPrefsManager(activity: Context) { 
private var prefs : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) 

fun getCurrentUser() = prefs.getString(SharedPrefsKeys.USER_UID, "").orEmpty() 

fun setCurrentUser(userId: String) { try { prefs.edit().putString(SharedPrefsKeys.USER_UID, userId).apply() } catch (e: Exception) { } } }

Logging in with an existing user is pretty similar to creating a new user. The code below is a wrapper for logging in an existing user

fun login(email: String, password: String) { 
if (email.isNotEmpty() && password.isNotEmpty()) { 
updateState(
 LoginViewState( 
progressType = ProgressType.Loading, 
isValidated = currentViewState().isValidated, 
userUID = currentViewState().userUID 
) ) 

auth.signInWithEmailAndPassword(email, password).addOnCompleteListener { 
if (it.isSuccessful) { 

// Assert Non null here because we had a 
// successful login as per result. **There are better ways** 
val authUser = auth.currentUser!!

db.reference.child("Users").child(authUser.uid).addListenerForSingleValueEvent( object : ValueEventListener { 
override fun onDataChange(dataSnapshot: DataSnapshot) { 

val data = dataSnapshot.getValue(User::class.java) 

if(data != null) { 
user = user?.copy( 
uid = authUser.uid, 
email = data.email, 
firstName = data.firstName, 
lastName = data.lastName 
) } 

updateState( 
LoginViewState( 
progressType = ProgressType.Result, 
isValidated = currentViewState().isValidated, 
userUID = authUser.uid 
) ) } 

override fun onCancelled(databaseError: DatabaseError) { 
updateState( 
LoginViewState( 
progressType = ProgressType.Failure, 
isValidated = currentViewState().isValidated, 
userUID = currentViewState().userUID ) ) } } 
) } else { 
updateState( 
LoginViewState( 
progressType = ProgressType.Failure, 
isValidated = currentViewState().isValidated, 
userUID = currentViewState().userUID ) ) } }
} else { 
updateState( 
LoginViewState( 
progressType = ProgressType.Failure, 
isValidated = currentViewState().isValidated, 
userUID = currentViewState().userUID ) ) } 
}

As you can see the format is close to the same but here is that instead of sending up first and last name data we must now fetch that data. We can do so by using the db.reference.child("Users").child(savedUID)

The code below is used to create a new user. In this project this codes lives in SignUpViewModel.kt which is a class that will handle the updates and changes to the view. This class will keep track of state and typically network calls are separated out into layers that this class talks to.

Creating a new User:

    
fun createNewUser(email: String, password: String, firstName: String, lastName: String) {

if (email.isNotEmpty() && password.isNotEmpty() && firstName.isNotEmpty() && lastName.isNotEmpty()) { 
updateState( 
SignUpViewState( 
progressType = ProgressType.Loading,
isValidated = currentViewState().isValidated )
)

 auth.createUserWithEmailAndPassword(email, password).addOnCompleteListener { 
if (it.isSuccessful) { 

// Assert Non null here because we had a 
// successful login as per result. **There are better ways** 
val user = auth.currentUser!! 

db.reference.child("Users").child(user.uid).setValue( 
hashMapOf<String, Any>( 
Pair("email", email), 
Pair("firstName", firstName), 
Pair("lastName", lastName) ) 
) 

updateState( 
SignUpViewState( 
progressType = ProgressType.Result, 
isValidated = currentViewState().isValidated ) 
)
} else { 
updateState( 
SignUpViewState( 
progressType = ProgressType.Failure, 
isValidated = currentViewState().isValidated ) ) }
}} else { 
updateState( 
SignUpViewState( 
progressType = ProgressType.Failure, 
isValidated = currentViewState().isValidated ) ) } 
}

The snippet above is a function that takes in 4 strings, one for an email, password, first name, and last name.

So if we have a valid email, and password we use the Firebase auth.createUserWithEmailAndPassword(email, password) method, within the callback (addOnCompleteListener) of the createNewUser method we check to see if an error was return. If there was an error we pass it along to our View State. You will notice that right now simple checks control our validation of input but we can have that live in the View Modle and persist through the View State as well. Currently I left the isValidated variable in but it is unused. Otherwise we unwrap the result, create a user with that result and pass the user to the next screen. **Something to consider not mentioned here is storing the uid of the user. Currently if the user exits the app after creating they will still have to restart and login.

Fetching User:

fun fetchUser(savedUID: String) { 
db.reference.child("Users").child(savedUID).addListenerForSingleValueEvent( object : ValueEventListener { 
override fun onDataChange(dataSnapshot: DataSnapshot) { 
val data = dataSnapshot.getValue(User::class.java) 

if (data != null) { 
user = user?.copy( 
uid = savedUID, 
email = data.email, 
firstName = data.firstName, 
lastName = data.lastName ) 
} 

mainProgressBar.visibility = GONE addFragmentToActivity(supportFragmentManager, ReminderView(), R.id.mainActivity) 
} 
override fun onCancelled(databaseError: DatabaseError) { } 
})
}

Before we can write a Reminder reminder we must first create the corresponding data type. Create a new file and name it Reminder.kt and add the following code to the new file.

data class Reminder ( 
var id : String? = null, 
var title : String? = "", 
var description : String? = "", 
var isComplete : Boolean? = false, 
var timestamp: Double? = null 
)

You may be wondering what the timestamp and member is for. When we are fetching our data from Firebase it comes back asynchronously which essentially means that the data that comes back may not be in the same order as it was before, so we can use the timestamp member to sort the data.

Before we write the code for this lets take a step back and think about how we want this data to be structured in our database. We will have a top level node called "Reminders". Now each user will have their own list of reminders so we can create another node keyed by the user's unique identifier. Each reminder will need it's own node so we can achieve keying the node by the reminder's id, and finally we can set the reminders data. This should look like so

Screen Shot 2019-06-16 at 7 33 52 PM

Let's see how this will look in code

Create Reminder

fun createReminder(uid: String, reminderID: String, title: String, description: String, timeStamp: Double) { 

val reminders = currentViewState().reminders 

updateState( 
ReminderViewState( 
progressType = ProgressType.Loading, 
isValidated = currentViewState().isValidated, 
reminders = reminders, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID )
) 

db.reference.child("Reminders").child(uid).child(reminderID).setValue( 
Reminder( 
reminderID, 
title = title, 
description = description, 
isComplete = false, 
timestamp = timeStamp )
).addOnCompleteListener { 
if (it.isSuccessful) { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Result, 
reminders = reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) 
) 
fetchReminders(uid) 
} else { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Failure, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) ) } } 
}

During successfully creating a reminder you can see that we fetch reminders from the database. Fetching is shown below.

Reading Reminders from the database. Much like how we have been communicating with the database we will with reading reminders from the database. We key into the Reminders then use the users uid. After which we can grab and sort our data from the database.

Fetching Reminders:

fun fetchReminders(uid: String) {
if (uid.isNotEmpty()) { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Loading, 
isValidated = currentViewState().isValidated, 
reminders = currentViewState().reminders, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) 
) 

db.reference.child("Reminders").child(uid).addListenerForSingleValueEvent( object : ValueEventListener { 

override fun onDataChange(dataSnapshot: DataSnapshot) { 
val data = dataSnapshot.children 
val reminders = mutableListOf<Reminder>() 

for (child in data) { 
val newReminder = child.getValue(Reminder::class.java)

if (newReminder != null) { 
reminders.add(newReminder) 
}
} 

reminders.sortBy { it.timestamp } 

updateState( 
ReminderViewState( 
progressType = ProgressType.Result, 
reminders = reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) 
) 
} 

override fun onCancelled(databaseError: DatabaseError) { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Failure, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) ) } } ) 
} else { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Failure, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) ) } 
}

Edit a reminder:

fun editReminder( uid: String, reminderID: String, title: String, description: String, isComplete: Boolean, timeStamp: Double ) { 

val reminders = currentViewState().reminders 

updateState( ReminderViewState( 
progressType = ProgressType.Loading, 
isValidated = currentViewState().isValidated, 
reminders = reminders, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) 
) 

db.reference.child("Reminders").child(uid).child(reminderID).setValue( 
Reminder( 
reminderID, 
title = title, 
description = description, 
isComplete = isComplete, 
timestamp = timeStamp ) 
).addOnCompleteListener { 
if (it.isSuccessful) { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Result, 
reminders = reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID 
) 
) 

fetchReminders(uid) 
} else { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Failure, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) ) } } 
}

Delete Reminder:

fun deleteReminder(uid: String, reminder: Reminder) { 
if (uid.isNotEmpty()) { 
db.reference.child("Reminders").child(uid).child(reminder.id!!).removeValue().addOnCompleteListener { 
if (it.isSuccessful) { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Result, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID )
 ) 
fetchReminders(uid) 
} else { 
updateState( 
ReminderViewState( 
progressType = ProgressType.Failure, 
reminders = currentViewState().reminders, 
isValidated = currentViewState().isValidated, 
markedForDeletion = currentViewState().markedForDeletion, 
markedForCompletion = currentViewState().markedForCompletion, 
userUID = currentViewState().userUID ) ) } } } 
}

To see how all these components all come together I encourage you to explore through the project! You have learned how to authenticate users using Firebase, and edit/write/read data using the FireBase Realtime Database. Using these concepts you create and support your own custom applications, and we are excited to see what you come up with; thanks for reading and I hope this helps you and your startup!

startupweekdemo's People

Contributors

andrewfoghel avatar theamazingbnd avatar etlasky avatar

Stargazers

Robert Chung avatar  avatar clickbox avatar  avatar

Watchers

 avatar

Forkers

etlasky

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.