From 90d9ce89de4fe845e2953e7169e3bddf8380736f Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 15:10:19 +0000 Subject: [PATCH 1/9] Use Navigation in Firestore Change-Id: I6aa24b3242690820ae15e8fc5042adb6de60dee8 --- build.gradle | 1 + firestore/app/build.gradle | 7 +- ...ctivityTest.java => MainFragmentTest.java} | 8 +- firestore/app/src/main/AndroidManifest.xml | 12 - .../example/fireeats/EntryChoiceActivity.kt | 3 +- .../fireeats/java/FilterDialogFragment.java | 4 +- .../example/fireeats/java/MainActivity.java | 321 +---------------- .../example/fireeats/java/MainFragment.java | 341 ++++++++++++++++++ .../fireeats/java/RatingDialogFragment.java | 4 +- ...ity.java => RestaurantDetailFragment.java} | 51 ++- .../fireeats/kotlin/FilterDialogFragment.kt | 4 +- .../example/fireeats/kotlin/MainActivity.kt | 274 +------------- .../example/fireeats/kotlin/MainFragment.kt | 277 ++++++++++++++ .../fireeats/kotlin/RatingDialogFragment.kt | 4 +- ...ctivity.kt => RestaurantDetailFragment.kt} | 56 ++- .../app/src/main/res/layout/activity_main.xml | 158 +------- .../src/main/res/layout/dialog_filters.xml | 4 +- .../app/src/main/res/layout/fragment_main.xml | 153 ++++++++ ...ail.xml => fragment_restaurant_detail.xml} | 0 .../main/res/navigation/nav_graph_java.xml | 30 ++ .../main/res/navigation/nav_graph_kotlin.xml | 30 ++ firestore/build.gradle | 3 +- 22 files changed, 931 insertions(+), 814 deletions(-) rename firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/{MainActivityTest.java => MainFragmentTest.java} (92%) create mode 100644 firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainFragment.java rename firestore/app/src/main/java/com/google/firebase/example/fireeats/java/{RestaurantDetailActivity.java => RestaurantDetailFragment.java} (85%) create mode 100644 firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt rename firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/{RestaurantDetailActivity.kt => RestaurantDetailFragment.kt} (80%) create mode 100644 firestore/app/src/main/res/layout/fragment_main.xml rename firestore/app/src/main/res/layout/{activity_restaurant_detail.xml => fragment_restaurant_detail.xml} (100%) create mode 100644 firestore/app/src/main/res/navigation/nav_graph_java.xml create mode 100644 firestore/app/src/main/res/navigation/nav_graph_kotlin.xml diff --git a/build.gradle b/build.gradle index 4ce962357..d517cb570 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.firebase:perf-plugin:1.3.5' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1' + classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.4' } } diff --git a/firestore/app/build.gradle b/firestore/app/build.gradle index c3f18f66d..70e280660 100644 --- a/firestore/app/build.gradle +++ b/firestore/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'androidx.navigation.safeargs' android { testBuildType "release" @@ -62,7 +63,7 @@ dependencies { // Support Libs implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.core:core:1.3.2' + implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.browser:browser:1.0.0' @@ -70,9 +71,11 @@ dependencies { implementation 'androidx.media:media:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.4' // Android architecture components - implementation 'androidx.lifecycle:lifecycle-runtime:2.3.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.3.0' diff --git a/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainActivityTest.java b/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java similarity index 92% rename from firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainActivityTest.java rename to firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java index 9682814f9..9faf47612 100644 --- a/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainActivityTest.java +++ b/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java @@ -11,7 +11,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.example.fireeats.java.MainActivity; +import com.google.firebase.example.fireeats.java.MainFragment; import org.junit.Assert; import org.junit.Before; @@ -22,10 +22,10 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation; import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; -@LargeTest @RunWith(AndroidJUnit4.class) public class MainActivityTest { +@LargeTest @RunWith(AndroidJUnit4.class) public class MainFragmentTest { - @Rule public ActivityTestRule mActivityTestRule = - new ActivityTestRule<>(MainActivity.class, false, false); + @Rule public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(MainFragment.class, false, false); UiDevice device; final long TIMEOUT = 300000; // Five minute timeout because our CI is slooow. diff --git a/firestore/app/src/main/AndroidManifest.xml b/firestore/app/src/main/AndroidManifest.xml index f17357263..33a73527f 100644 --- a/firestore/app/src/main/AndroidManifest.xml +++ b/firestore/app/src/main/AndroidManifest.xml @@ -22,22 +22,10 @@ - - - - - - - - diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt index d1babb98b..21e3c9325 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt @@ -3,6 +3,7 @@ package com.google.firebase.example.fireeats import android.content.Intent import com.firebase.example.internal.BaseEntryChoiceActivity import com.firebase.example.internal.Choice +import com.google.firebase.example.fireeats.kotlin.MainActivity class EntryChoiceActivity : BaseEntryChoiceActivity() { @@ -11,7 +12,7 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { Choice( "Java", "Run the Firestore quickstart written in Java.", - Intent(this, com.google.firebase.example.fireeats.java.MainActivity::class.java)), + Intent(this, MainActivity::class.java)), Choice( "Kotlin", "Run the Firestore quickstart written in Kotlin.", diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/FilterDialogFragment.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/FilterDialogFragment.java index e2a4f3eb4..595580177 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/FilterDialogFragment.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/FilterDialogFragment.java @@ -55,8 +55,8 @@ public void onDestroyView() { public void onAttach(Context context) { super.onAttach(context); - if (context instanceof FilterListener) { - mFilterListener = (FilterListener) context; + if (getParentFragment() instanceof FilterListener) { + mFilterListener = (FilterListener) getParentFragment(); } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java index 496b23579..a8677bf7f 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java @@ -1,331 +1,22 @@ package com.google.firebase.example.fireeats.java; -import android.content.DialogInterface; -import android.content.Intent; import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import androidx.core.text.HtmlCompat; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; +import androidx.navigation.Navigation; -import com.firebase.ui.auth.AuthUI; -import com.firebase.ui.auth.ErrorCodes; -import com.firebase.ui.auth.IdpResponse; -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.Task; -import com.google.android.material.snackbar.Snackbar; -import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.example.fireeats.R; -import com.google.firebase.example.fireeats.databinding.ActivityMainBinding; -import com.google.firebase.example.fireeats.java.adapter.RestaurantAdapter; -import com.google.firebase.example.fireeats.java.model.Rating; -import com.google.firebase.example.fireeats.java.model.Restaurant; -import com.google.firebase.example.fireeats.java.util.RatingUtil; -import com.google.firebase.example.fireeats.java.util.RestaurantUtil; -import com.google.firebase.example.fireeats.java.viewmodel.MainActivityViewModel; -import com.google.firebase.firestore.DocumentReference; -import com.google.firebase.firestore.DocumentSnapshot; -import com.google.firebase.firestore.FirebaseFirestore; -import com.google.firebase.firestore.FirebaseFirestoreException; -import com.google.firebase.firestore.Query; -import com.google.firebase.firestore.WriteBatch; -import java.util.Collections; -import java.util.List; - -public class MainActivity extends AppCompatActivity implements - FilterDialogFragment.FilterListener, - RestaurantAdapter.OnRestaurantSelectedListener, View.OnClickListener { - - private static final String TAG = "MainActivity"; - - private static final int RC_SIGN_IN = 9001; - - private static final int LIMIT = 50; - - private ActivityMainBinding mBinding; - - private FirebaseFirestore mFirestore; - private Query mQuery; - - private FilterDialogFragment mFilterDialog; - private RestaurantAdapter mAdapter; - - private MainActivityViewModel mViewModel; +public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mBinding = ActivityMainBinding.inflate(getLayoutInflater()); - setContentView(mBinding.getRoot()); - - setSupportActionBar(mBinding.toolbar); - - mBinding.filterBar.setOnClickListener(this); - mBinding.buttonClearFilter.setOnClickListener(this); - - // View model - mViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class); - - // Enable Firestore logging - FirebaseFirestore.setLoggingEnabled(true); - - // Firestore - mFirestore = FirebaseFirestore.getInstance(); - - // Get ${LIMIT} restaurants - mQuery = mFirestore.collection("restaurants") - .orderBy("avgRating", Query.Direction.DESCENDING) - .limit(LIMIT); - - // RecyclerView - mAdapter = new RestaurantAdapter(mQuery, this) { - @Override - protected void onDataChanged() { - // Show/hide content if the query returns empty. - if (getItemCount() == 0) { - mBinding.recyclerRestaurants.setVisibility(View.GONE); - mBinding.viewEmpty.setVisibility(View.VISIBLE); - } else { - mBinding.recyclerRestaurants.setVisibility(View.VISIBLE); - mBinding.viewEmpty.setVisibility(View.GONE); - } - } - - @Override - protected void onError(FirebaseFirestoreException e) { - // Show a snackbar on errors - Snackbar.make(mBinding.getRoot(), - "Error: check logs for info.", Snackbar.LENGTH_LONG).show(); - } - }; - - mBinding.recyclerRestaurants.setLayoutManager(new LinearLayoutManager(this)); - mBinding.recyclerRestaurants.setAdapter(mAdapter); - - // Filter Dialog - mFilterDialog = new FilterDialogFragment(); - } + setContentView(R.layout.activity_main); + setSupportActionBar(this.findViewById(R.id.toolbar)); - @Override - public void onStart() { - super.onStart(); - - // Start sign in if necessary - if (shouldStartSignIn()) { - startSignIn(); - return; - } - - // Apply filters - onFilter(mViewModel.getFilters()); - - // Start listening for Firestore updates - if (mAdapter != null) { - mAdapter.startListening(); - } - } - - @Override - public void onStop() { - super.onStop(); - if (mAdapter != null) { - mAdapter.stopListening(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_main, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_add_items: - onAddItemsClicked(); - break; - case R.id.menu_sign_out: - AuthUI.getInstance().signOut(this); - startSignIn(); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == RC_SIGN_IN) { - IdpResponse response = IdpResponse.fromResultIntent(data); - mViewModel.setIsSigningIn(false); - - if (resultCode != RESULT_OK) { - if (response == null) { - // User pressed the back button. - finish(); - } else if (response.getError() != null - && response.getError().getErrorCode() == ErrorCodes.NO_NETWORK) { - showSignInErrorDialog(R.string.message_no_network); - } else { - showSignInErrorDialog(R.string.message_unknown); - } - } - } - } - - public void onFilterClicked() { - // Show the dialog containing filter options - mFilterDialog.show(getSupportFragmentManager(), FilterDialogFragment.TAG); - } - - public void onClearFilterClicked() { - mFilterDialog.resetFilters(); - - onFilter(Filters.getDefault()); - } - - @Override - public void onRestaurantSelected(DocumentSnapshot restaurant) { - // Go to the details page for the selected restaurant - Intent intent = new Intent(this, RestaurantDetailActivity.class); - intent.putExtra(RestaurantDetailActivity.KEY_RESTAURANT_ID, restaurant.getId()); - - startActivity(intent); - overridePendingTransition(R.anim.slide_in_from_right, R.anim.slide_out_to_left); - } - - @Override - public void onFilter(Filters filters) { - // Construct query basic query - Query query = mFirestore.collection("restaurants"); - - // Category (equality filter) - if (filters.hasCategory()) { - query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.getCategory()); - } - - // City (equality filter) - if (filters.hasCity()) { - query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.getCity()); - } - - // Price (equality filter) - if (filters.hasPrice()) { - query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.getPrice()); - } - - // Sort by (orderBy with direction) - if (filters.hasSortBy()) { - query = query.orderBy(filters.getSortBy(), filters.getSortDirection()); - } - - // Limit items - query = query.limit(LIMIT); - - // Update the query - mAdapter.setQuery(query); - - // Set header - mBinding.textCurrentSearch.setText(HtmlCompat.fromHtml(filters.getSearchDescription(this), - HtmlCompat.FROM_HTML_MODE_LEGACY)); - mBinding.textCurrentSortBy.setText(filters.getOrderDescription(this)); - - // Save filters - mViewModel.setFilters(filters); - } - - private boolean shouldStartSignIn() { - return (!mViewModel.getIsSigningIn() && FirebaseAuth.getInstance().getCurrentUser() == null); - } - - private void startSignIn() { - // Sign in with FirebaseUI - Intent intent = AuthUI.getInstance().createSignInIntentBuilder() - .setAvailableProviders(Collections.singletonList( - new AuthUI.IdpConfig.EmailBuilder().build())) - .setIsSmartLockEnabled(false) - .build(); - - startActivityForResult(intent, RC_SIGN_IN); - mViewModel.setIsSigningIn(true); - } - - private void onAddItemsClicked() { - // Add a bunch of random restaurants - WriteBatch batch = mFirestore.batch(); - for (int i = 0; i < 10; i++) { - DocumentReference restRef = mFirestore.collection("restaurants").document(); - - // Create random restaurant / ratings - Restaurant randomRestaurant = RestaurantUtil.getRandom(this); - List randomRatings = RatingUtil.getRandomList(randomRestaurant.getNumRatings()); - randomRestaurant.setAvgRating(RatingUtil.getAverageRating(randomRatings)); - - // Add restaurant - batch.set(restRef, randomRestaurant); - - // Add ratings to subcollection - for (Rating rating : randomRatings) { - batch.set(restRef.collection("ratings").document(), rating); - } - } - - batch.commit().addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "Write batch succeeded."); - } else { - Log.w(TAG, "write batch failed.", task.getException()); - } - } - }); - } - - private void showSignInErrorDialog(@StringRes int message) { - AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle(R.string.title_sign_in_error) - .setMessage(message) - .setCancelable(false) - .setPositiveButton(R.string.option_retry, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - startSignIn(); - } - }) - .setNegativeButton(R.string.option_exit, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - finish(); - } - }).create(); - - dialog.show(); - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.filterBar: - onFilterClicked(); - break; - case R.id.buttonClearFilter: - onClearFilterClicked(); - break; - } + Navigation.findNavController(this, R.id.nav_host_fragment) + .setGraph(R.navigation.nav_graph_java); } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainFragment.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainFragment.java new file mode 100644 index 000000000..9f01773c1 --- /dev/null +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainFragment.java @@ -0,0 +1,341 @@ +package com.google.firebase.example.fireeats.java; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.text.HtmlCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavAction; +import androidx.navigation.NavController; +import androidx.navigation.NavOptions; +import androidx.navigation.NavOptionsBuilder; +import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.firebase.ui.auth.AuthUI; +import com.firebase.ui.auth.ErrorCodes; +import com.firebase.ui.auth.IdpResponse; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.android.material.snackbar.Snackbar; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.example.fireeats.R; +import com.google.firebase.example.fireeats.databinding.FragmentMainBinding; +import com.google.firebase.example.fireeats.java.adapter.RestaurantAdapter; +import com.google.firebase.example.fireeats.java.model.Rating; +import com.google.firebase.example.fireeats.java.model.Restaurant; +import com.google.firebase.example.fireeats.java.util.RatingUtil; +import com.google.firebase.example.fireeats.java.util.RestaurantUtil; +import com.google.firebase.example.fireeats.java.viewmodel.MainActivityViewModel; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.WriteBatch; + +import java.util.Collections; +import java.util.List; + +public class MainFragment extends Fragment implements + FilterDialogFragment.FilterListener, + RestaurantAdapter.OnRestaurantSelectedListener, View.OnClickListener { + + private static final String TAG = "MainActivity"; + + private static final int RC_SIGN_IN = 9001; + + private static final int LIMIT = 50; + + private FragmentMainBinding mBinding; + + private FirebaseFirestore mFirestore; + private Query mQuery; + + private FilterDialogFragment mFilterDialog; + private RestaurantAdapter mAdapter; + + private MainActivityViewModel mViewModel; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mBinding = FragmentMainBinding.inflate(inflater, container, false); + return mBinding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mBinding.filterBar.setOnClickListener(this); + mBinding.buttonClearFilter.setOnClickListener(this); + + // View model + mViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class); + + // Enable Firestore logging + FirebaseFirestore.setLoggingEnabled(true); + + // Firestore + mFirestore = FirebaseFirestore.getInstance(); + + // Get ${LIMIT} restaurants + mQuery = mFirestore.collection("restaurants") + .orderBy("avgRating", Query.Direction.DESCENDING) + .limit(LIMIT); + + // RecyclerView + mAdapter = new RestaurantAdapter(mQuery, this) { + @Override + protected void onDataChanged() { + // Show/hide content if the query returns empty. + if (getItemCount() == 0) { + mBinding.recyclerRestaurants.setVisibility(View.GONE); + mBinding.viewEmpty.setVisibility(View.VISIBLE); + } else { + mBinding.recyclerRestaurants.setVisibility(View.VISIBLE); + mBinding.viewEmpty.setVisibility(View.GONE); + } + } + + @Override + protected void onError(FirebaseFirestoreException e) { + // Show a snackbar on errors + Snackbar.make(mBinding.getRoot(), + "Error: check logs for info.", Snackbar.LENGTH_LONG).show(); + } + }; + + mBinding.recyclerRestaurants.setLayoutManager(new LinearLayoutManager(requireContext())); + mBinding.recyclerRestaurants.setAdapter(mAdapter); + + // Filter Dialog + mFilterDialog = new FilterDialogFragment(); + } + + @Override + public void onStart() { + super.onStart(); + + // Start sign in if necessary + if (shouldStartSignIn()) { + startSignIn(); + return; + } + + // Apply filters + onFilter(mViewModel.getFilters()); + + // Start listening for Firestore updates + if (mAdapter != null) { + mAdapter.startListening(); + } + } + + @Override + public void onStop() { + super.onStop(); + if (mAdapter != null) { + mAdapter.stopListening(); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_main, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_add_items: + onAddItemsClicked(); + break; + case R.id.menu_sign_out: + AuthUI.getInstance().signOut(requireContext()); + startSignIn(); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == RC_SIGN_IN) { + IdpResponse response = IdpResponse.fromResultIntent(data); + mViewModel.setIsSigningIn(false); + + if (resultCode != Activity.RESULT_OK) { + if (response == null) { + // User pressed the back button. + requireActivity().finish(); + } else if (response.getError() != null + && response.getError().getErrorCode() == ErrorCodes.NO_NETWORK) { + showSignInErrorDialog(R.string.message_no_network); + } else { + showSignInErrorDialog(R.string.message_unknown); + } + } + } + } + + public void onFilterClicked() { + // Show the dialog containing filter options + mFilterDialog.show(getChildFragmentManager(), FilterDialogFragment.TAG); + } + + public void onClearFilterClicked() { + mFilterDialog.resetFilters(); + + onFilter(Filters.getDefault()); + } + + @Override + public void onRestaurantSelected(DocumentSnapshot restaurant) { + // Go to the details page for the selected restaurant + MainFragmentDirections.ActionMainFragmentToRestaurantDetailFragment action = MainFragmentDirections + .actionMainFragmentToRestaurantDetailFragment(restaurant.getId()); + + NavHostFragment.findNavController(this) + .navigate(action); + } + + @Override + public void onFilter(Filters filters) { + // Construct query basic query + Query query = mFirestore.collection("restaurants"); + + // Category (equality filter) + if (filters.hasCategory()) { + query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.getCategory()); + } + + // City (equality filter) + if (filters.hasCity()) { + query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.getCity()); + } + + // Price (equality filter) + if (filters.hasPrice()) { + query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.getPrice()); + } + + // Sort by (orderBy with direction) + if (filters.hasSortBy()) { + query = query.orderBy(filters.getSortBy(), filters.getSortDirection()); + } + + // Limit items + query = query.limit(LIMIT); + + // Update the query + mAdapter.setQuery(query); + + // Set header + mBinding.textCurrentSearch.setText(HtmlCompat.fromHtml(filters.getSearchDescription(requireContext()), + HtmlCompat.FROM_HTML_MODE_LEGACY)); + mBinding.textCurrentSortBy.setText(filters.getOrderDescription(requireContext())); + + // Save filters + mViewModel.setFilters(filters); + } + + private boolean shouldStartSignIn() { + return (!mViewModel.getIsSigningIn() && FirebaseAuth.getInstance().getCurrentUser() == null); + } + + private void startSignIn() { + // Sign in with FirebaseUI + Intent intent = AuthUI.getInstance().createSignInIntentBuilder() + .setAvailableProviders(Collections.singletonList( + new AuthUI.IdpConfig.EmailBuilder().build())) + .setIsSmartLockEnabled(false) + .build(); + + startActivityForResult(intent, RC_SIGN_IN); + mViewModel.setIsSigningIn(true); + } + + private void onAddItemsClicked() { + // Add a bunch of random restaurants + WriteBatch batch = mFirestore.batch(); + for (int i = 0; i < 10; i++) { + DocumentReference restRef = mFirestore.collection("restaurants").document(); + + // Create random restaurant / ratings + Restaurant randomRestaurant = RestaurantUtil.getRandom(requireContext()); + List randomRatings = RatingUtil.getRandomList(randomRestaurant.getNumRatings()); + randomRestaurant.setAvgRating(RatingUtil.getAverageRating(randomRatings)); + + // Add restaurant + batch.set(restRef, randomRestaurant); + + // Add ratings to subcollection + for (Rating rating : randomRatings) { + batch.set(restRef.collection("ratings").document(), rating); + } + } + + batch.commit().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "Write batch succeeded."); + } else { + Log.w(TAG, "write batch failed.", task.getException()); + } + } + }); + } + + private void showSignInErrorDialog(@StringRes int message) { + AlertDialog dialog = new AlertDialog.Builder(requireContext()) + .setTitle(R.string.title_sign_in_error) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(R.string.option_retry, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + startSignIn(); + } + }) + .setNegativeButton(R.string.option_exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + requireActivity().finish(); + } + }).create(); + + dialog.show(); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.filterBar: + onFilterClicked(); + break; + case R.id.buttonClearFilter: + onClearFilterClicked(); + break; + } + } +} diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RatingDialogFragment.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RatingDialogFragment.java index d8db89f9f..b0dd21e96 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RatingDialogFragment.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RatingDialogFragment.java @@ -57,8 +57,8 @@ public void onDestroyView() { public void onAttach(Context context) { super.onAttach(context); - if (context instanceof RatingListener) { - mRatingListener = (RatingListener) context; + if (getParentFragment() instanceof RatingListener) { + mRatingListener = (RatingListener) getParentFragment(); } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailActivity.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailFragment.java similarity index 85% rename from firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailActivity.java rename to firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailFragment.java index d4242271c..6eee3cb82 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailActivity.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/RestaurantDetailFragment.java @@ -3,11 +3,15 @@ import android.content.Context; import android.os.Bundle; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import com.bumptech.glide.Glide; @@ -16,7 +20,7 @@ import com.google.android.gms.tasks.Task; import com.google.android.material.snackbar.Snackbar; import com.google.firebase.example.fireeats.R; -import com.google.firebase.example.fireeats.databinding.ActivityRestaurantDetailBinding; +import com.google.firebase.example.fireeats.databinding.FragmentRestaurantDetailBinding; import com.google.firebase.example.fireeats.java.adapter.RatingAdapter; import com.google.firebase.example.fireeats.java.model.Rating; import com.google.firebase.example.fireeats.java.model.Restaurant; @@ -30,14 +34,12 @@ import com.google.firebase.firestore.Query; import com.google.firebase.firestore.Transaction; -public class RestaurantDetailActivity extends AppCompatActivity +public class RestaurantDetailFragment extends Fragment implements EventListener, RatingDialogFragment.RatingListener, View.OnClickListener { private static final String TAG = "RestaurantDetail"; - public static final String KEY_RESTAURANT_ID = "key_restaurant_id"; - - private ActivityRestaurantDetailBinding mBinding; + private FragmentRestaurantDetailBinding mBinding; private RatingDialogFragment mRatingDialog; @@ -47,20 +49,21 @@ public class RestaurantDetailActivity extends AppCompatActivity private RatingAdapter mRatingAdapter; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mBinding = FragmentRestaurantDetailBinding.inflate(inflater, container, false); + return mBinding.getRoot(); + } + @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mBinding = ActivityRestaurantDetailBinding.inflate(getLayoutInflater()); - setContentView(mBinding.getRoot()); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); mBinding.restaurantButtonBack.setOnClickListener(this); mBinding.fabShowRatingDialog.setOnClickListener(this); - // Get restaurant ID from extras - String restaurantId = getIntent().getExtras().getString(KEY_RESTAURANT_ID); - if (restaurantId == null) { - throw new IllegalArgumentException("Must pass extra " + KEY_RESTAURANT_ID); - } + String restaurantId = RestaurantDetailFragmentArgs.fromBundle(getArguments()).getKeyRestaurantId(); // Initialize Firestore mFirestore = FirebaseFirestore.getInstance(); @@ -87,7 +90,7 @@ protected void onDataChanged() { } } }; - mBinding.recyclerRatings.setLayoutManager(new LinearLayoutManager(this)); + mBinding.recyclerRatings.setLayoutManager(new LinearLayoutManager(requireContext())); mBinding.recyclerRatings.setAdapter(mRatingAdapter); mRatingDialog = new RatingDialogFragment(); @@ -113,12 +116,6 @@ public void onStop() { } } - @Override - public void finish() { - super.finish(); - overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right); - } - /** * Listener for the Restaurant document ({@link #mRestaurantRef}). */ @@ -147,18 +144,18 @@ private void onRestaurantLoaded(Restaurant restaurant) { } public void onBackArrowClicked(View view) { - onBackPressed(); + requireActivity().onBackPressed(); } public void onAddRatingClicked(View view) { - mRatingDialog.show(getSupportFragmentManager(), RatingDialogFragment.TAG); + mRatingDialog.show(getChildFragmentManager(), RatingDialogFragment.TAG); } @Override public void onRating(Rating rating) { // In a transaction, add the new rating and update the aggregate totals addRating(mRestaurantRef, rating) - .addOnSuccessListener(this, new OnSuccessListener() { + .addOnSuccessListener(requireActivity(), new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "Rating added"); @@ -168,7 +165,7 @@ public void onSuccess(Void aVoid) { mBinding.recyclerRatings.smoothScrollToPosition(0); } }) - .addOnFailureListener(this, new OnFailureListener() { + .addOnFailureListener(requireActivity(), new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Add rating failed", e); @@ -212,9 +209,9 @@ public Void apply(Transaction transaction) throws FirebaseFirestoreException { } private void hideKeyboard() { - View view = getCurrentFocus(); + View view = requireActivity().getCurrentFocus(); if (view != null) { - ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) + ((InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) .hideSoftInputFromWindow(view.getWindowToken(), 0); } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/FilterDialogFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/FilterDialogFragment.kt index 281203a11..5ff11aca0 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/FilterDialogFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/FilterDialogFragment.kt @@ -122,8 +122,8 @@ class FilterDialogFragment : DialogFragment() { override fun onAttach(context: Context) { super.onAttach(context) - if (context is FilterListener) { - filterListener = context + if (parentFragment is FilterListener) { + filterListener = parentFragment as FilterListener } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainActivity.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainActivity.kt index 91f4b3d6f..5ff7f3654 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainActivity.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainActivity.kt @@ -1,276 +1,16 @@ package com.google.firebase.example.fireeats.kotlin -import android.app.Activity -import android.content.Intent import android.os.Bundle -import android.util.Log -import android.view.Menu -import android.view.MenuItem -import android.view.View -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.core.text.HtmlCompat -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import com.firebase.ui.auth.AuthUI -import com.firebase.ui.auth.ErrorCodes -import com.firebase.ui.auth.IdpResponse -import com.google.android.material.snackbar.Snackbar -import com.google.firebase.auth.ktx.auth +import androidx.navigation.Navigation import com.google.firebase.example.fireeats.R -import com.google.firebase.example.fireeats.databinding.ActivityMainBinding -import com.google.firebase.example.fireeats.kotlin.adapter.RestaurantAdapter -import com.google.firebase.example.fireeats.kotlin.model.Restaurant -import com.google.firebase.example.fireeats.kotlin.util.RatingUtil -import com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil -import com.google.firebase.example.fireeats.kotlin.viewmodel.MainActivityViewModel -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.FirebaseFirestoreException -import com.google.firebase.firestore.Query -import com.google.firebase.firestore.ktx.firestore -import com.google.firebase.ktx.Firebase - -class MainActivity : AppCompatActivity(), - FilterDialogFragment.FilterListener, - RestaurantAdapter.OnRestaurantSelectedListener { - - lateinit var firestore: FirebaseFirestore - lateinit var query: Query - - private lateinit var binding: ActivityMainBinding - private lateinit var filterDialog: FilterDialogFragment - lateinit var adapter: RestaurantAdapter - - private lateinit var viewModel: MainActivityViewModel +class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - setSupportActionBar(binding.toolbar) - - // View model - viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java) - - // Enable Firestore logging - FirebaseFirestore.setLoggingEnabled(true) - - // Firestore - firestore = Firebase.firestore - - // Get ${LIMIT} restaurants - query = firestore.collection("restaurants") - .orderBy("avgRating", Query.Direction.DESCENDING) - .limit(LIMIT.toLong()) - - // RecyclerView - adapter = object : RestaurantAdapter(query, this@MainActivity) { - override fun onDataChanged() { - // Show/hide content if the query returns empty. - if (itemCount == 0) { - binding.recyclerRestaurants.visibility = View.GONE - binding.viewEmpty.visibility = View.VISIBLE - } else { - binding.recyclerRestaurants.visibility = View.VISIBLE - binding.viewEmpty.visibility = View.GONE - } - } - - override fun onError(e: FirebaseFirestoreException) { - // Show a snackbar on errors - Snackbar.make(binding.root, - "Error: check logs for info.", Snackbar.LENGTH_LONG).show() - } - } - - binding.recyclerRestaurants.layoutManager = LinearLayoutManager(this) - binding.recyclerRestaurants.adapter = adapter - - // Filter Dialog - filterDialog = FilterDialogFragment() - - binding.filterBar.setOnClickListener { onFilterClicked() } - binding.buttonClearFilter.setOnClickListener { onClearFilterClicked() } - } - - public override fun onStart() { - super.onStart() - - // Start sign in if necessary - if (shouldStartSignIn()) { - startSignIn() - return - } - - // Apply filters - onFilter(viewModel.filters) - - // Start listening for Firestore updates - adapter.startListening() - } - - public override fun onStop() { - super.onStop() - adapter.stopListening() - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - return super.onCreateOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_add_items -> onAddItemsClicked() - R.id.menu_sign_out -> { - AuthUI.getInstance().signOut(this) - startSignIn() - } - } - return super.onOptionsItemSelected(item) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == RC_SIGN_IN) { - val response = IdpResponse.fromResultIntent(data) - viewModel.isSigningIn = false - - if (resultCode != Activity.RESULT_OK) { - if (response == null) { - // User pressed the back button. - finish() - } else if (response.error != null && response.error!!.errorCode == ErrorCodes.NO_NETWORK) { - showSignInErrorDialog(R.string.message_no_network) - } else { - showSignInErrorDialog(R.string.message_unknown) - } - } - } - } - - private fun onFilterClicked() { - // Show the dialog containing filter options - filterDialog.show(supportFragmentManager, FilterDialogFragment.TAG) - } - - private fun onClearFilterClicked() { - filterDialog.resetFilters() - - onFilter(Filters.default) - } - - override fun onRestaurantSelected(restaurant: DocumentSnapshot) { - // Go to the details page for the selected restaurant - val intent = Intent(this, RestaurantDetailActivity::class.java) - intent.putExtra(RestaurantDetailActivity.KEY_RESTAURANT_ID, restaurant.id) - - startActivity(intent) - overridePendingTransition(R.anim.slide_in_from_right, R.anim.slide_out_to_left) - } - - override fun onFilter(filters: Filters) { - // Construct query basic query - var query: Query = firestore.collection("restaurants") - - // Category (equality filter) - if (filters.hasCategory()) { - query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category) - } - - // City (equality filter) - if (filters.hasCity()) { - query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city) - } - - // Price (equality filter) - if (filters.hasPrice()) { - query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price) - } - - // Sort by (orderBy with direction) - if (filters.hasSortBy()) { - query = query.orderBy(filters.sortBy.toString(), filters.sortDirection) - } - - // Limit items - query = query.limit(LIMIT.toLong()) - - // Update the query - adapter.setQuery(query) - - // Set header - binding.textCurrentSearch.text = HtmlCompat.fromHtml(filters.getSearchDescription(this), - HtmlCompat.FROM_HTML_MODE_LEGACY) - binding.textCurrentSortBy.text = filters.getOrderDescription(this) - - // Save filters - viewModel.filters = filters - } - - private fun shouldStartSignIn(): Boolean { - return !viewModel.isSigningIn && Firebase.auth.currentUser == null - } - - private fun startSignIn() { - // Sign in with FirebaseUI - val intent = AuthUI.getInstance().createSignInIntentBuilder() - .setAvailableProviders(listOf(AuthUI.IdpConfig.EmailBuilder().build())) - .setIsSmartLockEnabled(false) - .build() - - startActivityForResult(intent, RC_SIGN_IN) - viewModel.isSigningIn = true - } - - private fun onAddItemsClicked() { - // Add a bunch of random restaurants - val batch = firestore.batch() - for (i in 0..9) { - val restRef = firestore.collection("restaurants").document() - - // Create random restaurant / ratings - val randomRestaurant = RestaurantUtil.getRandom(this) - val randomRatings = RatingUtil.getRandomList(randomRestaurant.numRatings) - randomRestaurant.avgRating = RatingUtil.getAverageRating(randomRatings) - - // Add restaurant - batch.set(restRef, randomRestaurant) - - // Add ratings to subcollection - for (rating in randomRatings) { - batch.set(restRef.collection("ratings").document(), rating) - } - } - - batch.commit().addOnCompleteListener { task -> - if (task.isSuccessful) { - Log.d(TAG, "Write batch succeeded.") - } else { - Log.w(TAG, "write batch failed.", task.exception) - } - } - } - - private fun showSignInErrorDialog(@StringRes message: Int) { - val dialog = AlertDialog.Builder(this) - .setTitle(R.string.title_sign_in_error) - .setMessage(message) - .setCancelable(false) - .setPositiveButton(R.string.option_retry) { _, _ -> startSignIn() } - .setNegativeButton(R.string.option_exit) { _, _ -> finish() }.create() - - dialog.show() - } - - companion object { - - private const val TAG = "MainActivity" - - private const val RC_SIGN_IN = 9001 - - private const val LIMIT = 50 + setContentView(R.layout.activity_main) + setSupportActionBar(findViewById(R.id.toolbar)) + Navigation.findNavController(this, R.id.nav_host_fragment) + .setGraph(R.navigation.nav_graph_kotlin) } -} +} \ No newline at end of file diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt new file mode 100644 index 000000000..ced055dde --- /dev/null +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt @@ -0,0 +1,277 @@ +package com.google.firebase.example.fireeats.kotlin + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.* +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.core.text.HtmlCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.firebase.ui.auth.AuthUI +import com.firebase.ui.auth.ErrorCodes +import com.firebase.ui.auth.IdpResponse +import com.google.android.material.snackbar.Snackbar +import com.google.firebase.auth.ktx.auth +import com.google.firebase.example.fireeats.R +import com.google.firebase.example.fireeats.databinding.FragmentMainBinding +import com.google.firebase.example.fireeats.kotlin.adapter.RestaurantAdapter +import com.google.firebase.example.fireeats.kotlin.model.Restaurant +import com.google.firebase.example.fireeats.kotlin.util.RatingUtil +import com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil +import com.google.firebase.example.fireeats.kotlin.viewmodel.MainActivityViewModel +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.FirebaseFirestoreException +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.ktx.Firebase + +class MainFragment : Fragment(), + FilterDialogFragment.FilterListener, + RestaurantAdapter.OnRestaurantSelectedListener { + + lateinit var firestore: FirebaseFirestore + lateinit var query: Query + + private lateinit var binding: FragmentMainBinding + private lateinit var filterDialog: FilterDialogFragment + lateinit var adapter: RestaurantAdapter + + private lateinit var viewModel: MainActivityViewModel + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FragmentMainBinding.inflate(inflater, container, false); + return binding.root; + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // View model + viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java) + + // Enable Firestore logging + FirebaseFirestore.setLoggingEnabled(true) + + // Firestore + firestore = Firebase.firestore + + // Get ${LIMIT} restaurants + query = firestore.collection("restaurants") + .orderBy("avgRating", Query.Direction.DESCENDING) + .limit(LIMIT.toLong()) + + // RecyclerView + adapter = object : RestaurantAdapter(query, this@MainFragment) { + override fun onDataChanged() { + // Show/hide content if the query returns empty. + if (itemCount == 0) { + binding.recyclerRestaurants.visibility = View.GONE + binding.viewEmpty.visibility = View.VISIBLE + } else { + binding.recyclerRestaurants.visibility = View.VISIBLE + binding.viewEmpty.visibility = View.GONE + } + } + + override fun onError(e: FirebaseFirestoreException) { + // Show a snackbar on errors + Snackbar.make(binding.root, + "Error: check logs for info.", Snackbar.LENGTH_LONG).show() + } + } + + binding.recyclerRestaurants.layoutManager = LinearLayoutManager(context) + binding.recyclerRestaurants.adapter = adapter + + // Filter Dialog + filterDialog = FilterDialogFragment() + + binding.filterBar.setOnClickListener { onFilterClicked() } + binding.buttonClearFilter.setOnClickListener { onClearFilterClicked() } + } + + public override fun onStart() { + super.onStart() + + // Start sign in if necessary + if (shouldStartSignIn()) { + startSignIn() + return + } + + // Apply filters + onFilter(viewModel.filters) + + // Start listening for Firestore updates + adapter.startListening() + } + + public override fun onStop() { + super.onStop() + adapter.stopListening() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_main, menu) + return super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_add_items -> onAddItemsClicked() + R.id.menu_sign_out -> { + AuthUI.getInstance().signOut(requireContext()) + startSignIn() + } + } + return super.onOptionsItemSelected(item) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == RC_SIGN_IN) { + val response = IdpResponse.fromResultIntent(data) + viewModel.isSigningIn = false + + if (resultCode != Activity.RESULT_OK) { + if (response == null) { + // User pressed the back button. + requireActivity().finish() + } else if (response.error != null && response.error!!.errorCode == ErrorCodes.NO_NETWORK) { + showSignInErrorDialog(R.string.message_no_network) + } else { + showSignInErrorDialog(R.string.message_unknown) + } + } + } + } + + private fun onFilterClicked() { + // Show the dialog containing filter options + filterDialog.show(childFragmentManager, FilterDialogFragment.TAG) + } + + private fun onClearFilterClicked() { + filterDialog.resetFilters() + + onFilter(Filters.default) + } + + override fun onRestaurantSelected(restaurant: DocumentSnapshot) { + // Go to the details page for the selected restaurant + val action = MainFragmentDirections + .actionMainFragmentToRestaurantDetailFragment(restaurant.id) + + findNavController().navigate(action) + } + + override fun onFilter(filters: Filters) { + // Construct query basic query + var query: Query = firestore.collection("restaurants") + + // Category (equality filter) + if (filters.hasCategory()) { + query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category) + } + + // City (equality filter) + if (filters.hasCity()) { + query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city) + } + + // Price (equality filter) + if (filters.hasPrice()) { + query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price) + } + + // Sort by (orderBy with direction) + if (filters.hasSortBy()) { + query = query.orderBy(filters.sortBy.toString(), filters.sortDirection) + } + + // Limit items + query = query.limit(LIMIT.toLong()) + + // Update the query + adapter.setQuery(query) + + // Set header + binding.textCurrentSearch.text = HtmlCompat.fromHtml(filters.getSearchDescription(requireContext()), + HtmlCompat.FROM_HTML_MODE_LEGACY) + binding.textCurrentSortBy.text = filters.getOrderDescription(requireContext()) + + // Save filters + viewModel.filters = filters + } + + private fun shouldStartSignIn(): Boolean { + return !viewModel.isSigningIn && Firebase.auth.currentUser == null + } + + private fun startSignIn() { + // Sign in with FirebaseUI + val intent = AuthUI.getInstance().createSignInIntentBuilder() + .setAvailableProviders(listOf(AuthUI.IdpConfig.EmailBuilder().build())) + .setIsSmartLockEnabled(false) + .build() + + startActivityForResult(intent, RC_SIGN_IN) + viewModel.isSigningIn = true + } + + private fun onAddItemsClicked() { + // Add a bunch of random restaurants + val batch = firestore.batch() + for (i in 0..9) { + val restRef = firestore.collection("restaurants").document() + + // Create random restaurant / ratings + val randomRestaurant = RestaurantUtil.getRandom(requireContext()) + val randomRatings = RatingUtil.getRandomList(randomRestaurant.numRatings) + randomRestaurant.avgRating = RatingUtil.getAverageRating(randomRatings) + + // Add restaurant + batch.set(restRef, randomRestaurant) + + // Add ratings to subcollection + for (rating in randomRatings) { + batch.set(restRef.collection("ratings").document(), rating) + } + } + + batch.commit().addOnCompleteListener { task -> + if (task.isSuccessful) { + Log.d(TAG, "Write batch succeeded.") + } else { + Log.w(TAG, "write batch failed.", task.exception) + } + } + } + + private fun showSignInErrorDialog(@StringRes message: Int) { + val dialog = AlertDialog.Builder(requireContext()) + .setTitle(R.string.title_sign_in_error) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(R.string.option_retry) { _, _ -> startSignIn() } + .setNegativeButton(R.string.option_exit) { _, _ -> requireActivity().finish() }.create() + + dialog.show() + } + + companion object { + + private const val TAG = "MainActivity" + + private const val RC_SIGN_IN = 9001 + + private const val LIMIT = 50 + } +} diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RatingDialogFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RatingDialogFragment.kt index ba7cc6399..11b77a404 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RatingDialogFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RatingDialogFragment.kt @@ -46,8 +46,8 @@ class RatingDialogFragment : DialogFragment() { override fun onAttach(context: Context) { super.onAttach(context) - if (context is RatingListener) { - ratingListener = context + if (parentFragment is RatingListener) { + ratingListener = parentFragment as RatingListener } } diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailActivity.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt similarity index 80% rename from firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailActivity.kt rename to firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt index c58bb07d1..8de7a30c4 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailActivity.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt @@ -3,51 +3,50 @@ package com.google.firebase.example.fireeats.kotlin import android.content.Context import android.os.Bundle import android.util.Log +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat.getSystemService +import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide import com.google.android.gms.tasks.Task import com.google.android.material.snackbar.Snackbar import com.google.firebase.example.fireeats.R -import com.google.firebase.example.fireeats.databinding.ActivityRestaurantDetailBinding +import com.google.firebase.example.fireeats.databinding.FragmentRestaurantDetailBinding import com.google.firebase.example.fireeats.kotlin.adapter.RatingAdapter import com.google.firebase.example.fireeats.kotlin.model.Rating import com.google.firebase.example.fireeats.kotlin.model.Restaurant import com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil -import com.google.firebase.firestore.DocumentReference -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.EventListener -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.FirebaseFirestoreException -import com.google.firebase.firestore.ListenerRegistration -import com.google.firebase.firestore.Query +import com.google.firebase.firestore.* import com.google.firebase.firestore.ktx.firestore import com.google.firebase.firestore.ktx.toObject import com.google.firebase.ktx.Firebase -class RestaurantDetailActivity : AppCompatActivity(), +class RestaurantDetailFragment : Fragment(), EventListener, RatingDialogFragment.RatingListener { private var ratingDialog: RatingDialogFragment? = null - private lateinit var binding: ActivityRestaurantDetailBinding + private lateinit var binding: FragmentRestaurantDetailBinding private lateinit var firestore: FirebaseFirestore private lateinit var restaurantRef: DocumentReference private lateinit var ratingAdapter: RatingAdapter private var restaurantRegistration: ListenerRegistration? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityRestaurantDetailBinding.inflate(layoutInflater) - setContentView(binding.root) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FragmentRestaurantDetailBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) // Get restaurant ID from extras - val restaurantId = intent.extras?.getString(KEY_RESTAURANT_ID) - ?: throw IllegalArgumentException("Must pass extra $KEY_RESTAURANT_ID") + val restaurantId = RestaurantDetailFragmentArgs.fromBundle(requireArguments()).keyRestaurantId // Initialize Firestore firestore = Firebase.firestore @@ -73,7 +72,7 @@ class RestaurantDetailActivity : AppCompatActivity(), } } } - binding.recyclerRatings.layoutManager = LinearLayoutManager(this) + binding.recyclerRatings.layoutManager = LinearLayoutManager(context) binding.recyclerRatings.adapter = ratingAdapter ratingDialog = RatingDialogFragment() @@ -98,11 +97,6 @@ class RestaurantDetailActivity : AppCompatActivity(), restaurantRegistration = null } - override fun finish() { - super.finish() - overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right) - } - /** * Listener for the Restaurant document ([.restaurantRef]). */ @@ -135,29 +129,30 @@ class RestaurantDetailActivity : AppCompatActivity(), } private fun onBackArrowClicked() { - onBackPressed() + requireActivity().onBackPressed() } private fun onAddRatingClicked() { - ratingDialog?.show(supportFragmentManager, RatingDialogFragment.TAG) + ratingDialog?.show(childFragmentManager, RatingDialogFragment.TAG) } override fun onRating(rating: Rating) { // In a transaction, add the new rating and update the aggregate totals addRating(restaurantRef, rating) - .addOnSuccessListener(this) { + .addOnSuccessListener(requireActivity()) { Log.d(TAG, "Rating added") // Hide keyboard and scroll to top hideKeyboard() binding.recyclerRatings.smoothScrollToPosition(0) } - .addOnFailureListener(this) { e -> + .addOnFailureListener(requireActivity()) { e -> Log.w(TAG, "Add rating failed", e) // Show failure message and hide keyboard hideKeyboard() - Snackbar.make(findViewById(android.R.id.content), "Failed to add rating", + Snackbar.make( + requireView().findViewById(android.R.id.content), "Failed to add rating", Snackbar.LENGTH_SHORT).show() } } @@ -193,9 +188,10 @@ class RestaurantDetailActivity : AppCompatActivity(), } private fun hideKeyboard() { - val view = currentFocus + // TODO + val view = requireActivity().currentFocus if (view != null) { - (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + (requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) .hideSoftInputFromWindow(view.windowToken, 0) } } diff --git a/firestore/app/src/main/res/layout/activity_main.xml b/firestore/app/src/main/res/layout/activity_main.xml index 5279edeec..571ea7f12 100644 --- a/firestore/app/src/main/res/layout/activity_main.xml +++ b/firestore/app/src/main/res/layout/activity_main.xml @@ -1,11 +1,11 @@ - + xmlns:tools="http://schemas.android.com/tools" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:viewBindingIgnore="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/toolbar" /> - + \ No newline at end of file diff --git a/firestore/app/src/main/res/layout/dialog_filters.xml b/firestore/app/src/main/res/layout/dialog_filters.xml index 870596650..c086f8807 100644 --- a/firestore/app/src/main/res/layout/dialog_filters.xml +++ b/firestore/app/src/main/res/layout/dialog_filters.xml @@ -76,7 +76,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/firestore/app/src/main/res/layout/activity_restaurant_detail.xml b/firestore/app/src/main/res/layout/fragment_restaurant_detail.xml similarity index 100% rename from firestore/app/src/main/res/layout/activity_restaurant_detail.xml rename to firestore/app/src/main/res/layout/fragment_restaurant_detail.xml diff --git a/firestore/app/src/main/res/navigation/nav_graph_java.xml b/firestore/app/src/main/res/navigation/nav_graph_java.xml new file mode 100644 index 000000000..77c4fec18 --- /dev/null +++ b/firestore/app/src/main/res/navigation/nav_graph_java.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml b/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml new file mode 100644 index 000000000..9efb595d9 --- /dev/null +++ b/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/firestore/build.gradle b/firestore/build.gradle index 909a2ecef..86fc38b74 100644 --- a/firestore/build.gradle +++ b/firestore/build.gradle @@ -2,14 +2,15 @@ buildscript { repositories { - jcenter() google() + jcenter() mavenLocal() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.google.gms:google-services:4.3.5' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31' + classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.4' } } From 4692aecad5c21a3a5b7e5d3619a687a23946d5ed Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 15:11:58 +0000 Subject: [PATCH 2/9] Delete test Change-Id: Iabde582e48e5b0998870e96a5065004ba2006c2e --- .../example/fireeats/MainFragmentTest.java | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java diff --git a/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java b/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java deleted file mode 100644 index 9faf47612..000000000 --- a/firestore/app/src/androidTest/java/com/google/firebase/example/fireeats/MainFragmentTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.google.firebase.example.fireeats; - -import android.content.Intent; -import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; -import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.UiObject; -import androidx.test.uiautomator.UiScrollable; -import androidx.test.uiautomator.UiSelector; -import android.view.accessibility.AccessibilityWindowInfo; - -import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.example.fireeats.java.MainFragment; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static androidx.test.InstrumentationRegistry.getInstrumentation; -import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; - -@LargeTest @RunWith(AndroidJUnit4.class) public class MainFragmentTest { - - @Rule public ActivityTestRule mActivityTestRule = - new ActivityTestRule<>(MainFragment.class, false, false); - - UiDevice device; - final long TIMEOUT = 300000; // Five minute timeout because our CI is slooow. - - @Before public void before() { - // Sign out of any existing sessions - FirebaseAuth.getInstance().signOut(); - device = UiDevice.getInstance(getInstrumentation()); - } - - @Test public void testAddItemsAndReview() throws Exception { - mActivityTestRule.launchActivity(new Intent()); - - // Input email for account created in the setup.sh - getById("email").setText("test@mailinator.com"); - closeKeyboard(); - getById("button_next").clickAndWaitForNewWindow(TIMEOUT); - - //Input password - getById("password").setText("password"); - closeKeyboard(); - getById("button_done").clickAndWaitForNewWindow(TIMEOUT); - - // Add random items - getActionBarItem(new UiSelector().textContains("Add Random Items"), TIMEOUT).click(); - device.waitForIdle(TIMEOUT); - - // Click on the first restaurant - getById("recycler_restaurants").getChild(new UiSelector().index(0)) - .clickAndWaitForNewWindow(TIMEOUT); - - // Click add review - getById("fabShowRatingDialog").click(); - - //Write a review - getById("restaurant_form_text").setText("\uD83D\uDE0E\uD83D\uDE00"); - closeKeyboard(); - - //Submit the review - getById("restaurant_form_button").clickAndWaitForNewWindow(TIMEOUT); - - // Assert that the review exists - UiScrollable ratingsList = new UiScrollable(getIdSelector("recyclerRatings")); - ratingsList.waitForExists(TIMEOUT); - ratingsList.scrollToBeginning(100); - Assert.assertTrue( - getById("recyclerRatings") - .getChild(new UiSelector().text("\uD83D\uDE0E\uD83D\uDE00")) - .waitForExists(TIMEOUT)); - } - - private UiObject getById(String id) { - UiObject obj = device.findObject(getIdSelector(id)); - obj.waitForExists(TIMEOUT); - return obj; - } - - private UiSelector getIdSelector(String id) { - return new UiSelector().resourceId("com.google.firebase.example.fireeats:id/" + id); - } - - private void closeKeyboard() { - for (AccessibilityWindowInfo w : getInstrumentation().getUiAutomation().getWindows()) { - if (w.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD) { - device.pressBack(); - return; - } - } - } - - private UiObject getActionBarItem(UiSelector selector, long timeout) throws InterruptedException { - final long STEP_TIMEOUT = 5000; - UiObject item = device.findObject(selector); - - for (long i = 0; i < timeout; i += STEP_TIMEOUT) { - openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); - - if (item.waitForExists(STEP_TIMEOUT)) { - break; - } - } - - return item; - } -} From fb2fcbf9b664655eb065a0bbddae069b4342b7f8 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 15:26:45 +0000 Subject: [PATCH 3/9] Downgrade jcenter() Change-Id: I169cb0b1a227590255909b303a9394aea1ab7a6d --- analytics/build.gradle | 2 +- app-indexing/build.gradle | 6 +++--- auth/build.gradle | 2 +- config/build.gradle | 2 +- crash/build.gradle | 2 +- database/build.gradle | 2 +- functions/build.gradle | 2 +- storage/build.gradle | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/analytics/build.gradle b/analytics/build.gradle index 13eea4901..7da504df3 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/app-indexing/build.gradle b/app-indexing/build.gradle index 13eea4901..382cbac3c 100644 --- a/app-indexing/build.gradle +++ b/app-indexing/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' @@ -15,9 +15,9 @@ buildscript { allprojects { repositories { - //mavenLocal() must be listed at the top to facilitate testing + // mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/auth/build.gradle b/auth/build.gradle index b76b33748..2df188b5a 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/config/build.gradle b/config/build.gradle index bb9fb3a62..46f0d381b 100644 --- a/config/build.gradle +++ b/config/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/crash/build.gradle b/crash/build.gradle index 78fa52eb3..94cb11674 100644 --- a/crash/build.gradle +++ b/crash/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/database/build.gradle b/database/build.gradle index 0186fd8a8..a92c0888f 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/functions/build.gradle b/functions/build.gradle index 0186fd8a8..a92c0888f 100644 --- a/functions/build.gradle +++ b/functions/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' diff --git a/storage/build.gradle b/storage/build.gradle index fc09e1ccd..1ae960f63 100644 --- a/storage/build.gradle +++ b/storage/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - jcenter() mavenLocal() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' @@ -15,9 +15,9 @@ buildscript { allprojects { repositories { - jcenter() mavenLocal() google() + jcenter() } } From 70513ba8dede8d6af86477ee318bfb362f8fa450 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 15 Mar 2021 11:52:11 -0400 Subject: [PATCH 4/9] Update firestore/app/src/main/res/navigation/nav_graph_kotlin.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário Pereira Fernandes --- firestore/app/src/main/res/navigation/nav_graph_kotlin.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml b/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml index 9efb595d9..57f2f6a54 100644 --- a/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml +++ b/firestore/app/src/main/res/navigation/nav_graph_kotlin.xml @@ -2,7 +2,7 @@ - \ No newline at end of file + From 1c6eb6cac514a1ba6786f38ad1fb8d9eefb4f48c Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 15 Mar 2021 11:52:18 -0400 Subject: [PATCH 5/9] Update firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário Pereira Fernandes --- .../com/google/firebase/example/fireeats/java/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java index a8677bf7f..0e8a087d1 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java @@ -14,7 +14,7 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - setSupportActionBar(this.findViewById(R.id.toolbar)); + setSupportActionBar(findViewById(R.id.toolbar)); Navigation.findNavController(this, R.id.nav_host_fragment) .setGraph(R.navigation.nav_graph_java); From 9197edb178c30530f9d51dbe650674bbded89cc9 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 15:55:37 +0000 Subject: [PATCH 6/9] Review comments Change-Id: I82acd91dec0dc6119150dca7ef1888676de6587d --- .../com/google/firebase/example/fireeats/EntryChoiceActivity.kt | 2 +- .../com/google/firebase/example/fireeats/java/MainActivity.java | 2 +- .../example/fireeats/kotlin/RestaurantDetailFragment.kt | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt index 21e3c9325..e7726f468 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/EntryChoiceActivity.kt @@ -12,7 +12,7 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { Choice( "Java", "Run the Firestore quickstart written in Java.", - Intent(this, MainActivity::class.java)), + Intent(this, com.google.firebase.example.fireeats.java.MainActivity::class.java)), Choice( "Kotlin", "Run the Firestore quickstart written in Kotlin.", diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java index 0e8a087d1..a8677bf7f 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/java/MainActivity.java @@ -14,7 +14,7 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - setSupportActionBar(findViewById(R.id.toolbar)); + setSupportActionBar(this.findViewById(R.id.toolbar)); Navigation.findNavController(this, R.id.nav_host_fragment) .setGraph(R.navigation.nav_graph_java); diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt index 8de7a30c4..93badba26 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt @@ -188,7 +188,6 @@ class RestaurantDetailFragment : Fragment(), } private fun hideKeyboard() { - // TODO val view = requireActivity().currentFocus if (view != null) { (requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) From 95585a131910a9609c3b46a672f503d793803b81 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 17:05:41 +0000 Subject: [PATCH 7/9] More jcenter() downgrades Change-Id: I11124189fc3b03e78d91dbb031bfc14e630e070d --- analytics/build.gradle | 2 +- auth/build.gradle | 2 +- config/build.gradle | 2 +- crash/build.gradle | 2 +- database/build.gradle | 2 +- firestore/build.gradle | 2 +- functions/build.gradle | 2 +- messaging/build.gradle | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/analytics/build.gradle b/analytics/build.gradle index 7da504df3..53847afca 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -17,7 +17,7 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/auth/build.gradle b/auth/build.gradle index 2df188b5a..693acab59 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -17,8 +17,8 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/config/build.gradle b/config/build.gradle index 46f0d381b..47c5f9296 100644 --- a/config/build.gradle +++ b/config/build.gradle @@ -17,7 +17,7 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/crash/build.gradle b/crash/build.gradle index 94cb11674..169b757de 100644 --- a/crash/build.gradle +++ b/crash/build.gradle @@ -18,8 +18,8 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/database/build.gradle b/database/build.gradle index a92c0888f..e41879df6 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -17,8 +17,8 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/firestore/build.gradle b/firestore/build.gradle index 86fc38b74..76c2fa965 100644 --- a/firestore/build.gradle +++ b/firestore/build.gradle @@ -18,8 +18,8 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/functions/build.gradle b/functions/build.gradle index a92c0888f..e41879df6 100644 --- a/functions/build.gradle +++ b/functions/build.gradle @@ -17,8 +17,8 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } diff --git a/messaging/build.gradle b/messaging/build.gradle index de230dc32..47c5f9296 100644 --- a/messaging/build.gradle +++ b/messaging/build.gradle @@ -3,8 +3,8 @@ buildscript { repositories { mavenLocal() - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' @@ -17,7 +17,7 @@ allprojects { repositories { //mavenLocal() must be listed at the top to facilitate testing mavenLocal() - jcenter() google() + jcenter() } } From 5cba4ceeb2b6b50a74350b1c5b71f95ced13226b Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 18:34:44 +0000 Subject: [PATCH 8/9] wildcard import Change-Id: I484f2bf80af2cfc38c96518a7755461a9ffb5af9 --- .../firebase/example/fireeats/kotlin/MainFragment.kt | 6 ++++-- .../fireeats/kotlin/RestaurantDetailFragment.kt | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt index ced055dde..d2d41de6a 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt @@ -4,13 +4,15 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log -import android.view.* +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.text.HtmlCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.firebase.ui.auth.AuthUI diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt index 93badba26..8905be2a6 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/RestaurantDetailFragment.kt @@ -7,7 +7,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager -import androidx.core.content.ContextCompat.getSystemService import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide @@ -19,13 +18,19 @@ import com.google.firebase.example.fireeats.kotlin.adapter.RatingAdapter import com.google.firebase.example.fireeats.kotlin.model.Rating import com.google.firebase.example.fireeats.kotlin.model.Restaurant import com.google.firebase.example.fireeats.kotlin.util.RestaurantUtil -import com.google.firebase.firestore.* +import com.google.firebase.firestore.DocumentReference +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.EventListener +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.FirebaseFirestoreException +import com.google.firebase.firestore.ListenerRegistration +import com.google.firebase.firestore.Query import com.google.firebase.firestore.ktx.firestore import com.google.firebase.firestore.ktx.toObject import com.google.firebase.ktx.Firebase class RestaurantDetailFragment : Fragment(), - EventListener, + EventListener, RatingDialogFragment.RatingListener { private var ratingDialog: RatingDialogFragment? = null From 653212ee27d34bfee6b09dc4861150e64305a140 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Mar 2021 18:51:59 +0000 Subject: [PATCH 9/9] Fix compile Change-Id: Ia30bb29037720fe5252f8a07756e8810030435a8 --- .../com/google/firebase/example/fireeats/kotlin/MainFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt index d2d41de6a..f98c379c6 100644 --- a/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt +++ b/firestore/app/src/main/java/com/google/firebase/example/fireeats/kotlin/MainFragment.kt @@ -4,10 +4,12 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log +import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import android.view.ViewGroup import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.text.HtmlCompat