diff --git a/MainActivity.java b/MainActivity.java
new file mode 100644
index 000000000..ad85e5967
--- /dev/null
+++ b/MainActivity.java
@@ -0,0 +1,379 @@
+/**
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.firebase.udacity.friendlychat;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+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;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+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.Arrays;
+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;
+ private ProgressBar mProgressBar;
+ private ImageButton mPhotoPickerButton;
+ private EditText mMessageEditText;
+ private Button mSendButton;
+
+ 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;
+ private Uri downloadUrl;
+
+ @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);
+ mPhotoPickerButton = (ImageButton) findViewById(R.id.photoPickerButton);
+ mMessageEditText = (EditText) findViewById(R.id.messageEditText);
+ mSendButton = (Button) findViewById(R.id.sendButton);
+
+ // Initialize message ListView and its adapter
+ List friendlyMessages = new ArrayList<>();
+ mMessageAdapter = new MessageAdapter(this, R.layout.item_message, friendlyMessages);
+ mMessageListView.setAdapter(mMessageAdapter);
+
+ // Initialize provider list
+ List providers;
+ providers = new ArrayList<>();
+
+ // Initialize progress bar
+ mProgressBar.setVisibility(ProgressBar.INVISIBLE);
+
+ // ImagePickerButton shows an image picker to upload a image for a message
+ mPhotoPickerButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ 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);
+ }
+ });
+
+ // Enable Send button when there's text to send
+ mMessageEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ if (charSequence.toString().trim().length() > 0) {
+ mSendButton.setEnabled(true);
+ } else {
+ mSendButton.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ }
+ });
+ mMessageEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(DEFAULT_MSG_LENGTH_LIMIT)});
+
+ // Send button sends a message and clears the EditText
+ mSendButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ 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
+ // deprecated method .setProviders has been replaced follwing example code
+ // https://github.com/firebase/FirebaseUI-Android/tree/master/auth#adding-providers
+ onSignedOutCleanup();
+ startActivityForResult(
+ AuthUI.getInstance()
+ .createSignInIntentBuilder()
+ .setIsSmartLockEnabled(false)
+ .setAvailableProviders(Arrays.asList(
+ new AuthUI.IdpConfig.GoogleBuilder().build(),
+ new AuthUI.IdpConfig.EmailBuilder().build()))
+ .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 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/
+ final StorageReference photoRef = mChatPhotosStorageReference.child(selectedImageUri.getLastPathSegment());
+
+ // Upload file to Firebase Storage
+ photoRef.putFile(selectedImageUri)
+ .addOnSuccessListener(this, new OnSuccessListener() {
+ public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
+ // When the image has successfully uploaded, we get its download URL
+ // DONE fix deprecated method error .getDownloadUrl()
+ // .getDownloadUrl() method is deprecated
+ // Original example code-
+ // Uri downloadUrl = taskSnapshot.getDownloadUrl();
+ // Code snippet from stackoverflow-
+ // https://stackoverflow.com/a/50743192/8811523
+
+ photoRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Uri uri) {downloadUrl = uri;}
+ });
+
+ // 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
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem 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() {
+ @Override
+ public void onSuccess(Void aVoid) {
+ // Make the fetched config available
+ // via FirebaseRemoteConfig get 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);
+ }
+}
diff --git a/build.gradle b/build.gradle
index f5a38fc74..67d4c6805 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,14 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
+ // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
mavenLocal()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.2'
- classpath 'com.google.gms:google-services:3.0.0'
+ classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.google.gms:google-services:4.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -18,6 +19,7 @@ allprojects {
repositories {
jcenter()
mavenLocal()
+ google()
}
}