Android Tutorials : Firebase Joke Poster Android Application
Firebase is a powerful Google platform. It is a mobile platform that helps you quickly develop high-quality apps. Implementing Firebase in an android application is quick and easy. In this tutorial we will learn about some of the features of Firebase by creating an android application for posting Jokes. A user can sign-in to the application using his Google account and post as many as jokes he wants. The posted jokes are stored in the Firebase realtime database and are displayed to the user using android RecyclerView. Firebase SDK gives in-built support for RecyclerView. A posted Joke can be liked by other users of the application. Total count of the likes for a joke is maintained in the database.
Here’s the list of features Firebase offers :
- Realtime Database
- Hosting
- Authentication
- Storage
- Notifications
- Cloud Messaging
- Remote Config
- AdWords
- AdMob
- Test Lab
- Crash Reporting
- App Indexing
- Dynamic Links
- Invites
Let’s dive right into creating an android Joke poster application to learn more about Firebase. The steps are described in 5 detailed yet simple parts :
Note : Make sure you have the latest version of Google Play Services and Google repository installed in your Android SDK manager as shown below.
Part ! : Includes steps to create a new Android application and set up Firebase in it.
1. Create a new Android Project in Android Studio. Give it a name and select the Minimum SDK on which your app will run on. I chose API 16 : Android 4.1 (JELLY_BEAN).
2. When you are prompted to add an activity to your application choose Blank Activity and click on next button.
3. In the next step click on Finish and in few seconds your application should be loaded in Android Studio.
4. Open build.gradle(Module:App) file of your application and add the following dependencies inside dependencies section of build.gradle (Module:App) file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:design:23.4.0' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'de.hdodenhof:circleimageview:1.3.0' compile 'com.android.support:appcompat-v7:23.4.0' // Google compile 'com.google.android.gms:play-services-auth:9.2.1' // Firebase compile 'com.google.firebase:firebase-database:9.2.1' compile 'com.google.firebase:firebase-auth:9.2.1' compile 'com.google.firebase:firebase-config:9.2.1' compile 'com.google.firebase:firebase-messaging:9.2.1' compile 'com.google.firebase:firebase-crash:9.2.1' // Firebase UI compile 'com.firebaseui:firebase-ui-database:0.4.0' } |
5. Now add the packagingOptions directive to your build.gradle(Module : App) file as shown below. This is how your build.gradle (Module : App) will look now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
apply plugin: 'com.android.application' repositories { mavenLocal() flatDir { dirs 'libs' } } android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.androidbash.androidbashfirebaseupdated" minSdkVersion 16 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE-FIREBASE.txt' exclude 'META-INF/NOTICE' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:design:23.4.0' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'de.hdodenhof:circleimageview:1.3.0' compile 'com.android.support:appcompat-v7:23.4.0' // Google compile 'com.google.android.gms:play-services-auth:9.2.1' // Firebase compile 'com.google.firebase:firebase-database:9.2.1' compile 'com.google.firebase:firebase-auth:9.2.1' compile 'com.google.firebase:firebase-config:9.2.1' compile 'com.google.firebase:firebase-messaging:9.2.1' compile 'com.google.firebase:firebase-crash:9.2.1' // Firebase UI compile 'com.firebaseui:firebase-ui-database:0.4.0' } apply plugin: 'com.google.gms.google-services' |
6. The application requires following permissions to work. Your app might not work unless you add this permission to your AndroidManifest.xml file.
1 2 3 |
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INTERNET"/> |
Part !! : Includes Firebase account creation and getting google-services.json file from Firebase console
1. The first thing you need to do to get started with Firebase is sign up for a free account. You can use your Google account to sign in to your firebase account.
2. After clicking on ‘Sign In with Google’ you will be asked to allow the Firebase to view your Email and your basic profile. Click on Allow button to continue.
3. Once you are signed in Click on Go to your Console if you are not automatically redirected to your firebase console. Click on Create New Project button in your firebase console.
4. Now you can see an overview of your new Firebase project. Now click on Add Firebase to your Android App as shown below.
5. Now you need to enter your App details (your application’s package name) and also get SHA-1 finger print and paste it in the field as shown below.
Click on Add App button. Upon clicking it, a file named google-services.json gets downloaded to your machine.
6. Copy the google-services.json file and paste in the app folder of your Android application.
Note: If you forget to copy and paste google-services.json file you will get the following error when you build your app.
7. Open the build.gradle (project level) file of your android application and add classpath ‘com.google.gms:google-services:3.0.0’ in the dependencies section. Your build.gradle (project level) file will look as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() mavenLocal() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.google.gms:google-services:3.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() mavenLocal() } } |
8. Now add apply plugin: ‘com.google.gms.google-services’ at the bottom of your build.gradle (App level) file, just below dependencies section. Also check whether you have added the following dependencies in the dependencies section of build.gradle (App level). Click on sync now or re-build the android application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:design:23.4.0' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'de.hdodenhof:circleimageview:1.3.0' compile 'com.android.support:appcompat-v7:23.4.0' // Google compile 'com.google.android.gms:play-services-auth:9.2.1' // Firebase compile 'com.google.firebase:firebase-database:9.2.1' compile 'com.google.firebase:firebase-auth:9.2.1' compile 'com.google.firebase:firebase-config:9.2.1' compile 'com.google.firebase:firebase-messaging:9.2.1' compile 'com.google.firebase:firebase-crash:9.2.1' // Firebase UI compile 'com.firebaseui:firebase-ui-database:0.4.0' } apply plugin: 'com.google.gms.google-services' |
9. The following section in the Firebase console shows the overview of your project. Click on Database option in the left navigation menu.
10. Initially as there will be no users, your database will be empty and it looks as shown in the following image.
11. Go to Rules tab of Database section and the rules by default look as shown below. It gives read and write permissions to the authenticated users of your application. To learn more about Firebase rules please visit this link.
12. Now as we need to implement Google sign-in functionality in our android application we need to Enable Google authentication under Authentication Tab of your Firebase console. Click on Save once you enable it.
Now we are done with the Second part and let’s move on to the next part.
Part !!! : Includes modifications to build.gradle file and other XML files
1. At this point of time, the build.gradle (Module:app) file should look like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
apply plugin: 'com.android.application' repositories { mavenLocal() flatDir { dirs 'libs' } } android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.androidbash.androidbashfirebasejokeposter" minSdkVersion 16 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE-FIREBASE.txt' exclude 'META-INF/NOTICE' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:design:23.4.0' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'de.hdodenhof:circleimageview:1.3.0' compile 'com.android.support:appcompat-v7:23.4.0' // Google compile 'com.google.android.gms:play-services-auth:9.2.1' // Firebase compile 'com.google.firebase:firebase-database:9.2.1' compile 'com.google.firebase:firebase-auth:9.2.1' compile 'com.google.firebase:firebase-config:9.2.1' compile 'com.google.firebase:firebase-messaging:9.2.1' compile 'com.google.firebase:firebase-crash:9.2.1' // Firebase UI compile 'com.firebaseui:firebase-ui-database:0.4.0' } apply plugin: 'com.google.gms.google-services' |
2. And also, the build.gradle (Module:project) file should look like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { mavenLocal() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.google.gms:google-services:3.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenLocal() jcenter() } } |
2. After adding Internet and two other permissions to the manifest : AndroidManifest.xml looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androidbash.androidbashfirebasejokeposter" > <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SignInActivity" /> </application> </manifest> |
3. The colors.xml file in the values folder looks like this. You can change the colors to change the look of the application.
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="primary">#2196F3</color> <color name="primary_dark">#1976D2</color> <color name="primary_light">#BBDEFB</color> <color name="accent">#03A9F4</color> <color name="primary_text">#FFFFFF</color> <color name="secondary_text">#727272</color> <color name="icons">#FFFFFF</color> <color name="divider">#B6B6B6</color> </resources> |
4. The menu_main.xml file in the menu folder looks like this. Menu includes a sign out button in it.
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/sign_out_menu" android:title="@string/sign_out" app:showAsAction="never"/> </menu> |
5. The styles.xml file in the values folder looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/primary</item> <item name="colorPrimaryDark">@color/primary_dark</item> <item name="colorAccent">@color/accent</item> <!--It is okay to get error under following line--> <item name="android:colorButtonNormal">@drawable/button_selector</item> <item name="colorButtonNormal">@drawable/button_selector</item> <item name="android:buttonStyle">@style/NewButtonStyle</item> </style> <style name="NewButtonStyle" parent="Widget.AppCompat.Button"> <item name="android:textColor">@color/primary_text</item> </style> </resources> |
6. The strings.xml file in the values folder looks like this. Always keep a habit of including all your xml string resources in this file.
1 2 3 4 5 6 7 8 |
<resources><string name="app_name">AndroidBash Joke Poster</string> <string name="welcome_msg">Welcome to Learning @ AndroidBash </string> <string name="sign_out">Sign Out</string> <string name="sign_in">Sign In</string> <string name="sign_up">Sign Up</string> <string name="loading">Loading</string> <string name="post">Post Joke</string> </resources> |
Part !V : Includes creation of new XML layout files and modification to activity_main.xml file
1. Create a new XML layout resource file and name it as activity_sign_in.xml. It includes a sign-in button and a progress bar in it. The activity_sign_in.xml file of our application has the following code in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/sign_in_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <ProgressBar android:id="@+id/progress_bar_sign_up" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="8dp" android:visibility="gone" /> <Button android:id="@+id/sign_in_button" style="?android:textAppearanceSmall" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Google Sign In" android:textColor="@android:color/white" android:textStyle="bold" /> </FrameLayout> |
2. Create a new XML layout resource file and name it as item_joke_layout.xml. It includes the layout for each joke posted in the app. The layout looks as shown in the image below.
The item_joke_layout.xml file of our application has the following code in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="10dp"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/circleImageView" android:layout_width="36dp" android:layout_height="36dp" android:src="@drawable/ic_face_black_24dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/circleImageView"> <TextView android:id="@+id/jokeTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/jokeAuthorTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/jokeTextView" android:layout_weight="0" android:textAppearance="?android:attr/textAppearanceSmall" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:gravity="center_horizontal" android:orientation="vertical"> <ImageButton android:id="@+id/like_button" android:layout_width="wrap_content" android:layout_height="fill_parent" android:background="#00ffffff" android:src="@drawable/like_button" /> <TextView android:id="@+id/like_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/like_button" android:text="0" android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> </RelativeLayout> </RelativeLayout> <Space android:layout_width="1dp" android:layout_height="35dp" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:background="#ccc" /> </RelativeLayout> |
3. The acivity_main.xml includes a RecyclerView to hold all the jokes posted to the application. Also it has an EditText field to enter the joke and a button upon clicking which the joke will be posted. The acivity_main.xml has the following code in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/mRecyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/linearLayout"/> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:id="@+id/linearLayout"> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:id="@+id/post_joke_et" android:layout_gravity="center_vertical" android:layout_weight="1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/post" android:enabled="false" android:id="@+id/button" android:layout_gravity="bottom"/> </LinearLayout> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/progressBar" style="?android:attr/progressBarStyleSmall" android:layout_centerVertical="true" android:layout_centerHorizontal="true"/> </RelativeLayout> |
Part V : Includes creation of new java classes and modification to MainActivity.java file
1. Create a new java class in your package and name it as Joke.java. Each Joke object will have fields such as uid, text, name, photoUrl, jokeKey, likeCount and a likesGivenBy map. Go through the comments written on top of the code to understand even better.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package com.androidbash.androidbashfirebasejokeposter; import com.google.firebase.database.Exclude; import java.util.HashMap; import java.util.Map; /* * Created By AndroidBash on 30/07/2016 */ public class Joke { //User Id of the logged in User public String uid; //Joke Text public String text; //User Name of the logged in User public String name; //Display Picture of the logged in User public String photoUrl; //Firebase assigns a unique key to every Joke posted public String jokeKey; //Total count of the likes for this Joke public int likeCount = 0; //Map to store the user ids of the User who has liked this joke public Map<String, Boolean> likesGivenBy = new HashMap<>(); public Joke() { } public Joke(String uid, String text, String name, String photoUrl, String jokeKey, Map likesGivenBy) { this.uid = uid; this.text = text; this.name = name; this.photoUrl = photoUrl; this.jokeKey = jokeKey; this.likesGivenBy = likesGivenBy; } @Exclude public Map<String, Object> toMap() { HashMap<String, Object> result = new HashMap<>(); result.put("uid", uid); result.put("name", name); result.put("text", text); result.put("photoUrl", photoUrl); result.put("jokeKey", jokeKey); result.put("likeCount", likeCount); result.put("likesGivenBy", likesGivenBy); return result; } } |
2. Create a new Java class in your package and name it as SignInActivity.java. It includes code to handle Google sign-in. Upon successful authentication you will be redirected to MainActivity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
package com.androidbash.androidbashfirebasejokeposter; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.auth.api.signin.GoogleSignInResult; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.auth.AuthCredential; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.GoogleAuthProvider; /* * Created By AndroidBash on 30/07/2016 */ public class SignInActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener, View.OnClickListener { private static final String TAG = "AndroidBash"; private static final int RC_SIGN_IN = 9001; private GoogleApiClient mGoogleApiClient; private FirebaseAuth mFirebaseAuth; private Button mSignInButton; private ProgressDialog mProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_in); // Sign In Button mSignInButton = (Button) findViewById(R.id.sign_in_button); // Set on click listeners for Sign In Button mSignInButton.setOnClickListener(this); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(getString(R.string.default_web_client_id)) .requestEmail() .build(); mGoogleApiClient = new GoogleApiClient.Builder(this) .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */) .addApi(Auth.GOOGLE_SIGN_IN_API, gso) .build(); // Initialize FirebaseAuth //You need to include google-services.json (downloaded from firebase console) file under the "app" folder of this project. mFirebaseAuth = FirebaseAuth.getInstance(); } private void handleFirebaseAuthResult(AuthResult authResult) { if (authResult != null) { // Welcome the user FirebaseUser user = authResult.getUser(); Toast.makeText(this, "Welcome " + user.getEmail(), Toast.LENGTH_SHORT).show(); // Go back to the main activity startActivity(new Intent(this, MainActivity.class)); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.sign_in_button: signIn(); break; default: return; } } private void signIn() { Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); startActivityForResult(signInIntent, RC_SIGN_IN); showProgressDialog(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...); if (requestCode == RC_SIGN_IN) { GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); if (result.isSuccess()) { // Google Sign In was successful, authenticate with Firebase GoogleSignInAccount account = result.getSignInAccount(); firebaseAuthWithGoogle(account); } else { if(mProgressDialog!=null){ hideProgressDialog(); } // Google Sign In failed Log.e(TAG, "Google Sign In failed."); } } } private void firebaseAuthWithGoogle(GoogleSignInAccount acct) { Log.d(TAG, "firebaseAuthWithGooogle:" + acct.getId()); AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null); mFirebaseAuth.signInWithCredential(credential) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful()); // If sign in fails, display a message to the user. If sign in succeeds // the auth state listener will be notified and logic to handle the // signed in user can be handled in the listener. if (!task.isSuccessful()) { Log.w(TAG, "signInWithCredential", task.getException()); Toast.makeText(SignInActivity.this, "Authentication failed.", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(SignInActivity.this, "Authentication Success. Please Wait", Toast.LENGTH_SHORT).show(); startActivity(new Intent(SignInActivity.this, MainActivity.class)); finish(); } hideProgressDialog(); } }); } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { // An unresolvable error has occurred and Google APIs (including Sign-In) will not // be available. Log.d(TAG, "onConnectionFailed:" + connectionResult); Toast.makeText(this, "Google Play Services error.", Toast.LENGTH_SHORT).show(); } public void showProgressDialog() { if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage(getString(R.string.loading)); mProgressDialog.setIndeterminate(true); } mProgressDialog.show(); } public void hideProgressDialog() { if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } } |
3. Open MainActivity.java and add the following code in it. Please go through the comment on top of the code to understand even better.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
package com.androidbash.androidbashfirebasejokeposter; import android.content.Intent; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; import com.bumptech.glide.Glide; import com.firebase.ui.database.FirebaseRecyclerAdapter; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.MutableData; import com.google.firebase.database.Transaction; import java.util.HashMap; import java.util.Map; import de.hdodenhof.circleimageview.CircleImageView; /* * Created By AndroidBash on 30/07/2016 */ public class MainActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener { private static final String TAG = "AndroidBash"; public static final String JOKES = "jokes"; private String mUsername; private String mPhotoUrl; private EditText mJokeEditText; private Button mPostButton; private RecyclerView mRecyclerView; private LinearLayoutManager mLinearLayoutManager; private FirebaseRecyclerAdapter<Joke, MyJokeViewHolder> mFirebaseAdapter; private ProgressBar mProgressBar; private DatabaseReference mFirebaseDatabaseReference; private FirebaseAuth mFirebaseAuth; private FirebaseUser mFirebaseUser; private GoogleApiClient mGoogleApiClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initialize Firebase Auth mFirebaseAuth = FirebaseAuth.getInstance(); mFirebaseUser = mFirebaseAuth.getCurrentUser(); if (mFirebaseUser == null) { // User has not signed in, launch the Sign In activity startActivity(new Intent(this, SignInActivity.class)); finish(); return; } else { mUsername = mFirebaseUser.getDisplayName(); if (mFirebaseUser.getPhotoUrl() != null) mPhotoUrl = mFirebaseUser.getPhotoUrl().toString(); } //The main entry point for Google Play services integration. Builder to configure a GoogleApiClient is required. mGoogleApiClient = new GoogleApiClient.Builder(this) .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */) .addApi(Auth.GOOGLE_SIGN_IN_API) .build(); mProgressBar = (ProgressBar) findViewById(R.id.progressBar); mRecyclerView = (RecyclerView) findViewById(R.id.mRecyclerView); mLinearLayoutManager = new LinearLayoutManager(this); mLinearLayoutManager.setStackFromEnd(true); //Gets a Reference to your Firebase Database. // You should have included a google-services.json (downloaded from Firebase console) under "app" folder of your project. mFirebaseDatabaseReference = FirebaseDatabase.getInstance().getReference(); //Adapter for Firebase RecyclerView : FirebaseRecyclerAdapter /** * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide * @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an * instance of the corresponding view with the data from an instance of modelClass. * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some * combination of <code>limit()</code>, <code>startAt()</code>, and <code>endAt() */ mFirebaseAdapter = new FirebaseRecyclerAdapter<Joke, MyJokeViewHolder>( Joke.class, R.layout.item_joke_layout, MyJokeViewHolder.class, mFirebaseDatabaseReference.child(JOKES)) { @Override protected void populateViewHolder(MyJokeViewHolder viewHolder, final Joke joke, int position) { /** * Each time the data at the given Firebase location changes, this method will be called for each item that needs * to be displayed. The first two arguments correspond to the mLayout and mModelClass given to the constructor of * this class. The third argument is the item's position in the list. * <p> * Your implementation should populate the view using the data contained in the model. * * @param viewHolder The view to populate * @param model The object containing the data used to populate the view * @param position The position in the list of the view being populated */ mProgressBar.setVisibility(ProgressBar.INVISIBLE); viewHolder.jokeTextView.setText(joke.text); viewHolder.jokeAuthorTextView.setText(joke.name); viewHolder.likeCount.setText(joke.likeCount + ""); //Before loading jokes from firebase database to the RecyclerView, we check whether the current user has //liked the joke or not. Depending on true/false we set the likes button. //likesGivenBy is a Map<String,Boolean> which stores uid and true/false (user has liked or not). if (joke.likesGivenBy.get(mFirebaseUser.getUid()) == null) { viewHolder.likeButton.setBackgroundResource(R.drawable.ic_favorite_border_black_24dp); } else { viewHolder.likeButton.setBackgroundResource(R.drawable.ic_favorite_black_24dp); } //If the User has no google profile picture we set a default image. if (joke.photoUrl == null) { viewHolder.circleImageView.setImageDrawable(ContextCompat.getDrawable(MainActivity.this, R.drawable.ic_face_black_24dp)); } else { //Loads the image in to circleImageView from the given URL Glide.with(MainActivity.this) .load(joke.photoUrl) .into(viewHolder.circleImageView); } //OnClickListener for like button. //runTransaction method : Runs a transaction on the data at this location. //Parameters: //handler - An object to handle running the transaction viewHolder.likeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mFirebaseDatabaseReference.child(JOKES).child(joke.jokeKey).runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData currentData) { /*Transaction.Result doTransaction(MutableData currentData) This method will be called, possibly multiple times, with the current data at this location. It is responsible for inspecting that data and returning a (Transaction.Result) specifying either the desired new data at the location or that the transaction should be aborted. Since this method may be called repeatedly for the same transaction, be extremely careful of any side effects that may be triggered by this method. In addition, this method is called from within the Firebase library's run loop, so care is also required when accessing data that may be in use by other threads in your application. Parameters: currentData - The current data at the location. Update this to the desired data at the location Returns: Either the new data, or an indication to abort the transaction*/ Joke joke = currentData.getValue(Joke.class); if (joke == null) { return Transaction.success(currentData); } if (joke.likesGivenBy.containsKey(mFirebaseUser.getUid())) { Log.i("AndroidBash", "User has already Liked. So it can be considered as Unliked."); joke.likeCount = joke.likeCount - 1; joke.likesGivenBy.remove(mFirebaseUser.getUid()); } else { Log.i("AndroidBash", "User Liked"); joke.likeCount = joke.likeCount + 1; joke.likesGivenBy.put(mFirebaseUser.getUid(), true); } // Set value and report transaction success currentData.setValue(joke); return Transaction.success(currentData); } @Override public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { // Transaction completed Log.d(TAG, "postTransaction:onComplete:" + databaseError); } }); } }); } }; mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); int jokeCount = mFirebaseAdapter.getItemCount(); int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition(); // If the recycler view is initially being loaded or the user is at the bottom of the list, scroll // to the bottom of the list to show the newly added message. if (lastVisiblePosition == -1 || (positionStart >= (jokeCount - 1) && lastVisiblePosition == (positionStart - 1))) { mRecyclerView.scrollToPosition(positionStart); } } }); mRecyclerView.setLayoutManager(mLinearLayoutManager); mRecyclerView.setAdapter(mFirebaseAdapter); mJokeEditText = (EditText) findViewById(R.id.post_joke_et); mJokeEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int j, int k) { } @Override public void onTextChanged(CharSequence charSequence, int i, int j, int k) { if (charSequence.toString().trim().length() > 0) { mPostButton.setEnabled(true); } else { mPostButton.setEnabled(false); } } @Override public void afterTextChanged(Editable editable) { } }); mPostButton = (Button) findViewById(R.id.button); mPostButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Map<String, Boolean> likesGivenBy = new HashMap<>(); //Key under which a joke will be stored. Firebase assigns a unique id for every new joke pushed. String key = mFirebaseDatabaseReference.child(JOKES).push().getKey(); //Creates a new Joke object Joke joke = new Joke(mFirebaseUser.getUid(), mJokeEditText.getText().toString(), mUsername, mPhotoUrl, key, likesGivenBy); //New Map will be created from a Joke object. Map<String, Object> values = joke.toMap(); //Creates a new Map and puts the Map (joke object) which is to be stored in Firebase Database. Map<String, Object> childUpdates = new HashMap<>(); childUpdates.put("/jokes/" + key, values); //updateChildren method inserts the Joke under "/jokes/{key}" node. mFirebaseDatabaseReference.updateChildren(childUpdates); //EditText filed will be set back "" once the joke is posted. mJokeEditText.setText(""); } }); } @Override public void onPause() { super.onPause(); } @Override public void onResume() { super.onResume(); } @Override public void onDestroy() { super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.sign_out_menu: mFirebaseAuth.signOut(); Auth.GoogleSignInApi.signOut(mGoogleApiClient); mFirebaseUser = null; mUsername = null; mPhotoUrl = null; startActivity(new Intent(this, SignInActivity.class)); finish(); return true; default: return super.onOptionsItemSelected(item); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.d(TAG, "onConnectionFailed:" + connectionResult); } public static class MyJokeViewHolder extends RecyclerView.ViewHolder { public TextView jokeTextView; public TextView jokeAuthorTextView; public CircleImageView circleImageView; public ImageButton likeButton; public TextView likeCount; public MyJokeViewHolder(View v) { super(v); jokeTextView = (TextView) itemView.findViewById(R.id.jokeTextView); jokeAuthorTextView = (TextView) itemView.findViewById(R.id.jokeAuthorTextView); circleImageView = (CircleImageView) itemView.findViewById(R.id.circleImageView); likeButton = (ImageButton) itemView.findViewById(R.id.like_button); likeCount = (TextView) itemView.findViewById(R.id.like_count); } } } |
The following image shows the Firebase database once a User signs up on our application and posts jokes. Every Joke posted will be saved under jokes nodes of your database.
That’s it!. If you have followed all the steps you can now Run your application and test how it works. The following image shows the series of activities, when the application runs on an Emulator. Each joke can be liked by any user who has signed-in to your application. The total count of likes for each joke is displayed.
You can download the project and add your google-services.json file (downloaded from Firebase console of your Firebase App) under app folder and try it out.
Here is the Video Demo to show the working of this application.
If you have followed our tutorial please do let us know by commenting below. It makes us write more and more articles on android application development. In case if you get stuck somewhere please do comment below and let us know. We usually reply within a day.
Note : Do not forget to :
Add google-services.json file under “app” folder.
Update Google Play Services and Google Repository to the latest versions available.
Enable Google Sign-In in the Auth tab of your Firebase console.
Get SHA-1 finger print and paste it during the creation of your firebase project.
Add all the required dependencies in the build.gradle file of your application.
help me i can’t sign in
Google Sign In failed. 9001
every thing is same your code.
Hello Ji,
1. Have you created a new firebase application in Firebase console by giving SHA-1 fingerprint ?
2. Make sure you have put google-services.json in the ‘app’ folder of your android application. Please check your build.gradle files and AndroidManifest.xml file.
3. Enable Google Sign-in in Auth tab of your firebase console.
thank you reply
but everything check ..
please check your source
It might be because of corrupted SHA-1 fingerprint. What you can do is, delete your firebase project and create a new one by using a newly generated SHA-1 fingerprint. To generate SHA-1 fingerprint from Android Studio follow this link : https://chandruscm.wordpress.com/tag/how-to-obtain-sha1-signing-certificate-fingerprint-from-android-studio/
If you dont want to delete your firebase project you can go to your firebase Project Settings in firebase console and add the newly generated SHA-1.
Download a new google-services.json file and paste it into your android application’s “app/” directory.
In you signinactivity
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
result always returns false, what to do?
I had similar issue few months back. It was because corrupted SHA-1 fingerprint. What i did was, I deleted my firebase project and created a new one by using a newly generated SHA-1 fingerprint. To generate SHA-1 fingerprint from Android Studio follow this link. .
If you dont want to delete your firebase project you can go to your firebase project settings in firebase console and
add the newly generated SHA-1.
Download a new google-services.json file and paste it into your android application’s “app/” directory.
Hi,
Thanks
I got a different SHA1 key by following your tutorial and it’s working
but now problem is that I’m able to post a joke only in the first run, after that the result.inSuccess returns false; If I uninstall my app completely and run again then it’s working, but for the first time only
Hello ,
I am getting the error like this…
Failed to load module descriptor class: Didn’t find class “com.google.android.gms.dynamite.descriptors.com.google.firebase.auth.ModuleDescriptor” on path: DexPathList[[zip file “/data/app/hb.com.firebasejokeposter-2/base.apk”],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
Tag Manager is not found and thus will not be used
Hello Dhaivat, Is this error affecting the applications’s behaviour? Also please update firebase sdk to 9.0.2. Also update Google Repository and Google Play Services, there is an update for them too.
How to bypass login screen once user has signed in. Everytime user opens app he has to click google sign in button
Solved this by redirecting to JokeActivity
You did a great job putting this together, It’s sad to see that the source is not working.
Please help fix this.
Genius work here btw
HI Peter, Thank you for your comment. Did you change google-services.json and try to run?
In my local it is still working fine. I will push the code to github and give a link to it soon. Thank you.
i’m new to android and firebase..can u plz explain how to add likes counter
This is my coding……..
mDatabaseLikes = FirebaseDatabase.getInstance().getReference().child(“Likes”);
viewHolder.mLikesBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mProcessLikes = true;
mDatabaseLikes.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (mProcessLikes) {
if (dataSnapshot.child(Post_key).hasChild(mAuth.getCurrentUser().getUid())) {
mDatabaseLikes.child(Post_key).child(mAuth.getCurrentUser().getUid()).removeValue();
mProcessLikes = false;
} else {
mDatabaseLikes.child(Post_key).child(mAuth.getCurrentUser().getUid()).setValue(“likes”);
mProcessLikes = false;
}
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
}
};
public static class BlogViewHolder extends RecyclerView.ViewHolder {
View mView;
ImageButton mLikesBtn;
DatabaseReference mDatabaseLike;
FirebaseAuth mAuth;
public BlogViewHolder(View itemView) {
super(itemView);
mView = itemView;
mLikesBtn = (ImageButton) mView.findViewById(R.id.LikeBtn);
mDatabaseLike = FirebaseDatabase.getInstance().getReference().child(“Likes”);
mAuth = FirebaseAuth.getInstance();
mDatabaseLike.keepSynced(true);
}
public void setLikesBtn(final String Post_key) {
mDatabaseLike.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.child(Post_key).hasChild(mAuth.getCurrentUser().getUid())) {
mLikesBtn.setImageResource(R.mipmap.ic_thumb_up_blue);
} else {
mLikesBtn.setImageResource(R.mipmap.ic_thumb_up);
}
}