Skip to content
14 changes: 13 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,16 @@ dependencies {

// Displaying images
compile 'com.github.bumptech.glide:glide:3.6.1'
}

// Firebase
compile 'com.google.firebase:firebase-database:9.6.0'
compile 'com.google.firebase:firebase-auth:9.6.0'
compile 'com.google.firebase:firebase-storage:9.6.0'
compile 'com.google.firebase:firebase-messaging:9.6.0'
compile 'com.google.firebase:firebase-config:9.6.0'

// FirebaseUI
compile 'com.firebaseui:firebase-ui-auth:0.6.0'
}

apply plugin: 'com.google.gms.google-services'
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
*/
package com.google.firebase.udacity.friendlychat;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
Expand All @@ -29,16 +33,39 @@
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.firebase.ui.auth.AuthUI;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
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.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

public static final String ANONYMOUS = "anonymous";
public static final int DEFAULT_MSG_LENGTH_LIMIT = 1000;
public static final String FRIENDLY_MSG_LENGTH_KEY = "friendly_msg_length";

public static final int RC_SIGN_IN = 1;
private static final int RC_PHOTO_PICKER = 2;

private ListView mMessageListView;
private MessageAdapter mMessageAdapter;
Expand All @@ -49,13 +76,32 @@ public class MainActivity extends AppCompatActivity {

private String mUsername;

// Firebase instance variables
private FirebaseDatabase mFirebaseDatabase;
private DatabaseReference mMessagesDatabaseReference;
private ChildEventListener mChildEventListener;
private FirebaseAuth mFirebaseAuth;
private FirebaseAuth.AuthStateListener mAuthStateListener;
private FirebaseStorage mFirebaseStorage;
private StorageReference mChatPhotosStorageReference;
private FirebaseRemoteConfig mFirebaseRemoteConfig;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mUsername = ANONYMOUS;

// Initialize Firebase components
mFirebaseDatabase = FirebaseDatabase.getInstance();
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseStorage = FirebaseStorage.getInstance();
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();

mMessagesDatabaseReference = mFirebaseDatabase.getReference().child("messages");
mChatPhotosStorageReference = mFirebaseStorage.getReference().child("chat_photos");

// Initialize references to views
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mMessageListView = (ListView) findViewById(R.id.messageListView);
Expand All @@ -75,7 +121,10 @@ protected void onCreate(Bundle savedInstanceState) {
mPhotoPickerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// TODO: Fire an intent to show an image picker
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
startActivityForResult(Intent.createChooser(intent, "Complete action using"), RC_PHOTO_PICKER);
}
});

Expand Down Expand Up @@ -104,12 +153,101 @@ public void afterTextChanged(Editable editable) {
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// TODO: Send messages on click
FriendlyMessage friendlyMessage = new FriendlyMessage(mMessageEditText.getText().toString(), mUsername, null);
mMessagesDatabaseReference.push().setValue(friendlyMessage);

// Clear input box
mMessageEditText.setText("");
}
});

mAuthStateListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
onSignedInInitialize(user.getDisplayName());
} else {
// User is signed out
onSignedOutCleanup();
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setIsSmartLockEnabled(false)
.setProviders(
AuthUI.EMAIL_PROVIDER,
AuthUI.GOOGLE_PROVIDER)
.build(),
RC_SIGN_IN);
}
}
};

// Create Remote Config Setting to enable developer mode.
// Fetching configs from the server is normally limited to 5 requests per hour.
// Enabling developer mode allows many more requests to be made per hour, so developers
// can test different config values during development.
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
mFirebaseRemoteConfig.setConfigSettings(configSettings);

// Define default config values. Defaults are used when fetched config values are not
// available. Eg: if an error occurred fetching values from the server.
Map<String, Object> defaultConfigMap = new HashMap<>();
defaultConfigMap.put(FRIENDLY_MSG_LENGTH_KEY, DEFAULT_MSG_LENGTH_LIMIT);
mFirebaseRemoteConfig.setDefaults(defaultConfigMap);
fetchConfig();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
if (resultCode == RESULT_OK) {
// Sign-in succeeded, set up the UI
Toast.makeText(this, "Signed in!", Toast.LENGTH_SHORT).show();
} else if (resultCode == RESULT_CANCELED) {
// Sign in was canceled by the user, finish the activity
Toast.makeText(this, "Sign in canceled", Toast.LENGTH_SHORT).show();
finish();
}
} else if (requestCode == RC_PHOTO_PICKER && resultCode == RESULT_OK) {
Uri selectedImageUri = data.getData();

// Get a reference to store file at chat_photos/<FILENAME>
StorageReference photoRef = mChatPhotosStorageReference.child(selectedImageUri.getLastPathSegment());

// Upload file to Firebase Storage
photoRef.putFile(selectedImageUri)
.addOnSuccessListener(this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
// When the image has successfully uploaded, we get its download URL
Uri downloadUrl = taskSnapshot.getDownloadUrl();

// Set the download URL to the message box, so that the user can send it to the database
FriendlyMessage friendlyMessage = new FriendlyMessage(null, mUsername, downloadUrl.toString());
mMessagesDatabaseReference.push().setValue(friendlyMessage);
}
});
}
}

@Override
protected void onResume() {
super.onResume();
mFirebaseAuth.addAuthStateListener(mAuthStateListener);
}

@Override
protected void onPause() {
super.onPause();
if (mAuthStateListener != null) {
mFirebaseAuth.removeAuthStateListener(mAuthStateListener);
}
mMessageAdapter.clear();
detachDatabaseReadListener();
}

@Override
Expand All @@ -121,6 +259,92 @@ public boolean onCreateOptionsMenu(Menu menu) {

@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.sign_out_menu:
AuthUI.getInstance().signOut(this);
return true;
default:
return super.onOptionsItemSelected(item);
}
}

private void onSignedInInitialize(String username) {
mUsername = username;
attachDatabaseReadListener();
}

private void onSignedOutCleanup() {
mUsername = ANONYMOUS;
mMessageAdapter.clear();
detachDatabaseReadListener();
}

private void attachDatabaseReadListener() {
if (mChildEventListener == null) {
mChildEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
FriendlyMessage friendlyMessage = dataSnapshot.getValue(FriendlyMessage.class);
mMessageAdapter.add(friendlyMessage);
}

public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
public void onChildRemoved(DataSnapshot dataSnapshot) {}
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
public void onCancelled(DatabaseError databaseError) {}
};
mMessagesDatabaseReference.addChildEventListener(mChildEventListener);
}
}

private void detachDatabaseReadListener() {
if (mChildEventListener != null) {
mMessagesDatabaseReference.removeEventListener(mChildEventListener);
mChildEventListener = null;
}
}

// Fetch the config to determine the allowed length of messages.
public void fetchConfig() {
long cacheExpiration = 3600; // 1 hour in seconds
// If developer mode is enabled reduce cacheExpiration to 0 so that each fetch goes to the
// server. This should not be used in release builds.
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Make the fetched config available
// via FirebaseRemoteConfig get<type> calls, e.g., getLong, getString.
mFirebaseRemoteConfig.activateFetched();

// Update the EditText length limit with
// the newly retrieved values from Remote Config.
applyRetrievedLengthLimit();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// An error occurred when fetching the config.
Log.w(TAG, "Error fetching config", e);

// Update the EditText length limit with
// the newly retrieved values from Remote Config.
applyRetrievedLengthLimit();
}
});
}

/**
* Apply retrieved length limit to edit text field. This result may be fresh from the server or it may be from
* cached values.
*/
private void applyRetrievedLengthLimit() {
Long friendly_msg_length = mFirebaseRemoteConfig.getLong(FRIENDLY_MSG_LENGTH_KEY);
mMessageEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(friendly_msg_length.intValue())});
Log.d(TAG, FRIENDLY_MSG_LENGTH_KEY + " = " + friendly_msg_length);
}
}
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
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
Expand Down