Skip to content
This repository was archived by the owner on Jul 2, 2025. It is now read-only.

Commit 849d656

Browse files
1. Create User in Firstore
2. Profile UI 3. Data binding for user name and email 4. Async Task class to Download and set user photo 5. Create ProfileViewModel
1 parent a7f9ed3 commit 849d656

19 files changed

+436
-106
lines changed

android/canonical/app/build.gradle

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ android {
4343
dependencies {
4444
implementation fileTree(dir: "libs", include: ["*.jar"])
4545
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
46-
implementation 'androidx.core:core-ktx:1.3.0'
46+
implementation 'androidx.core:core-ktx:1.3.1'
4747
implementation 'androidx.appcompat:appcompat:1.1.0'
4848
implementation "androidx.fragment:fragment-ktx:1.2.5"
4949
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@@ -52,14 +52,20 @@ dependencies {
5252
implementation 'com.google.android.gms:play-services-maps:17.0.0'
5353
implementation 'com.google.android.gms:play-services-location:17.0.0'
5454
implementation 'com.google.android.libraries.places:places:2.3.0'
55-
implementation "android.arch.lifecycle:extensions:1.1.1"
55+
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
5656
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
5757
implementation 'com.google.firebase:firebase-analytics:17.4.4'
5858
implementation 'com.google.firebase:firebase-auth:19.3.2'
5959
implementation 'com.google.firebase:firebase-firestore-ktx:21.4.3'
60+
61+
implementation 'com.google.android.material:material:1.1.0'
62+
implementation 'androidx.percentlayout:percentlayout:1.0.0'
63+
implementation 'androidx.cardview:cardview:1.0.0'
64+
6065
kapt 'com.android.databinding:compiler:3.1.4'
6166
testImplementation 'junit:junit:4.12'
6267
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
6368
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
6469

70+
6571
}

android/canonical/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
location permissions for the "MyLocation" functionality.
99
-->
1010
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
11+
<uses-permission android:name="android.permission.INTERNET"/>
1112

1213
<application
1314
android:allowBackup="true"

android/canonical/app/src/main/java/com/google/samples/quickstart/canonical/LoginFragment.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.google.samples.quickstart.canonical.SignInViewModel.Companion.GOOGLE_
2020
class LoginFragment : Fragment() {
2121

2222
private val signInVM: SignInViewModel by activityViewModels()
23+
private val profileVM : ProfileViewModel by activityViewModels()
2324

2425
private fun signIn() {
2526
val signInIntent = signInVM.getSignInIntent()
@@ -54,22 +55,8 @@ class LoginFragment : Fragment() {
5455
signInVM.getFirebaseAuthLogStatusLiveData().observe(this, Observer {
5556
when (it) {
5657
true -> {
57-
val curFirebaseUser = signInVM.getFirebaseAuthCurUser()
58-
val db = Firebase.firestore
59-
val dbFirebaseUser = hashMapOf(
60-
"UserID" to (curFirebaseUser?.uid ?: ""),
61-
"UserName" to "sdsdsds",
62-
"Email" to (curFirebaseUser?.email ?: "")
63-
)
64-
db.collection("RunUser").document(curFirebaseUser!!.uid)
65-
.set(dbFirebaseUser, SetOptions.merge())
66-
.addOnSuccessListener {
67-
Log.d(LOGIN_FRAGMENT_TAG, "DocumentSnapshot added with ID: ${curFirebaseUser.uid}")
68-
}
69-
.addOnFailureListener { e ->
70-
Log.w(LOGIN_FRAGMENT_TAG, "Error adding document", e)
71-
}
7258
Log.d(LOGIN_FRAGMENT_TAG, "firebaseUser is not null")
59+
// Start main activity
7360
val intent = Intent(context, MainActivity::class.java)
7461
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
7562
startActivity(intent)

android/canonical/app/src/main/java/com/google/samples/quickstart/canonical/MainActivity.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
1111
class MainActivity : AppCompatActivity() {
1212

1313
private val signInVM : SignInViewModel by viewModels()
14+
private val profileVM : ProfileViewModel by viewModels()
1415

1516
private fun setupNavigationBar() {
1617
supportFragmentManager
@@ -44,7 +45,7 @@ class MainActivity : AppCompatActivity() {
4445
R.id.bottom_navigation_item_profile -> {
4546
supportFragmentManager
4647
.beginTransaction()
47-
.replace(R.id.fragment_container, MeFragment())
48+
.replace(R.id.fragment_container, ProfileFragment())
4849
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
4950
.commit()
5051
true
@@ -69,11 +70,12 @@ class MainActivity : AppCompatActivity() {
6970
when (signInVM.isLogIn()) {
7071
true -> {
7172
Log.d(MAIN_ACTIVITY_TAG, "Already login")
73+
// Init Profile
74+
profileVM.initAppUser(signInVM.getFirebaseAuthCurUser()!!)
7275
}
7376

7477
false -> {
7578
Log.d(MAIN_ACTIVITY_TAG, "No login. Update UI")
76-
// signInVM.signInAccountInit()
7779
findViewById<ConstraintLayout>(R.id.main_activity_view).removeAllViews()
7880
supportFragmentManager
7981
.beginTransaction()

android/canonical/app/src/main/java/com/google/samples/quickstart/canonical/MeFragment.kt renamed to android/canonical/app/src/main/java/com/google/samples/quickstart/canonical/ProfileFragment.kt

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,36 @@ package com.google.samples.quickstart.canonical
33
import android.content.Intent
44
import android.os.Bundle
55
import android.util.Log
6-
import androidx.fragment.app.Fragment
76
import android.view.LayoutInflater
87
import android.view.View
98
import android.view.ViewGroup
109
import android.widget.Button
11-
import android.widget.TextView
10+
import androidx.fragment.app.Fragment
1211
import androidx.fragment.app.activityViewModels
1312
import androidx.lifecycle.Observer
13+
import androidx.databinding.DataBindingUtil
14+
import com.google.samples.quickstart.canonical.databinding.FragmentProfileBinding
1415

1516

16-
class MeFragment : Fragment() {
17+
class ProfileFragment : Fragment() {
1718

1819
private val signInVM: SignInViewModel by activityViewModels()
20+
private val profileVM : ProfileViewModel by activityViewModels()
1921
private lateinit var observer : Observer<Boolean>
22+
private lateinit var binding : FragmentProfileBinding
2023

2124
override fun onCreate(savedInstanceState: Bundle?) {
2225
super.onCreate(savedInstanceState)
23-
// Add login status change listener
26+
// Add login status change listener.
27+
// When firebaseUser is null, signing out successfully
2428
observer = Observer {
2529
when (it) {
2630
true -> {
27-
Log.d(ME_TAG, "firebaseUser is not null")
31+
Log.d(PROFILE_TAG, "firebaseUser is not null")
2832
}
2933

3034
false -> {
31-
Log.d(ME_TAG, "firebaseUser is null")
35+
Log.d(PROFILE_TAG, "firebaseUser is null")
3236
val intent = Intent(context, MainActivity::class.java)
3337
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
3438
startActivity(intent)
@@ -37,14 +41,19 @@ class MeFragment : Fragment() {
3741
}
3842
// set LifeCycle owner with MeFragment. Observe will be destroyed when MeFragment is destroyed
3943
signInVM.getFirebaseAuthLogStatusLiveData().observe(this, observer)
44+
profileVM.initAppUserStatistic()
4045
}
4146

4247
override fun onCreateView(
4348
inflater: LayoutInflater, container: ViewGroup?,
4449
savedInstanceState: Bundle?
4550
): View? {
4651
// Inflate the layout for this fragment
47-
return inflater.inflate(R.layout.fragment_me, container, false)
52+
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false)
53+
binding.lifecycleOwner = this
54+
binding.userName = profileVM.getUserName()
55+
binding.userEmail = profileVM.getUserEmail()
56+
return binding.root
4857
}
4958

5059
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -55,10 +64,19 @@ class MeFragment : Fragment() {
5564
signInVM.signOut()
5665
}
5766
// update UI
58-
view.findViewById<TextView>(R.id.textView)?.text = signInVM.getFirebaseAuthCurUser()?.email
67+
downloadPhotoAndSetView(view)
68+
}
69+
70+
private fun downloadPhotoAndSetView(view: View) {
71+
val url = profileVM.getUserPhotoURL()
72+
if (url != "") {
73+
Log.d(PROFILE_TAG, url)
74+
DownloadImageTask(view.findViewById(R.id.usr_img))
75+
.execute(url)
76+
}
5977
}
6078

6179
companion object {
62-
private const val ME_TAG = "MeFragment"
80+
private const val PROFILE_TAG = "ProfileFragment"
6381
}
64-
}
82+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.google.samples.quickstart.canonical
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
5+
import android.os.AsyncTask
6+
import android.util.Log
7+
import android.widget.ImageView
8+
import androidx.lifecycle.MutableLiveData
9+
import androidx.lifecycle.ViewModel
10+
import com.google.firebase.auth.FirebaseUser
11+
import com.google.firebase.firestore.ktx.firestore
12+
import com.google.firebase.ktx.Firebase
13+
import java.io.InputStream
14+
import java.net.URL
15+
16+
17+
class ProfileViewModel : ViewModel() {
18+
data class AppUser (
19+
var userName : String,
20+
var email : String,
21+
var uid : String,
22+
var googleAccountProfileUrl : String,
23+
var totalDistanceMeters : MutableLiveData<Long> = MutableLiveData(0),
24+
var totalEnergyCalories : MutableLiveData<Long> = MutableLiveData(0),
25+
var totalTimeMillisecond : MutableLiveData<Long> = MutableLiveData(0),
26+
var singleRunIDList : MutableLiveData<ArrayList<String>> = MutableLiveData(ArrayList())
27+
)
28+
29+
private lateinit var curAppUser: AppUser
30+
31+
private fun getUid() : String {
32+
return curAppUser.uid
33+
}
34+
35+
private fun setTotalDistanceMeters(totalDistanceMeters : Long) {
36+
curAppUser.totalDistanceMeters.value = totalDistanceMeters
37+
}
38+
39+
private fun setTotalEnergyCalories(totalEnergyCalories : Long) {
40+
curAppUser.totalEnergyCalories.value = totalEnergyCalories
41+
}
42+
43+
private fun setTotalTimeMillisecond(totalTimeMillisecond : Long) {
44+
curAppUser.totalTimeMillisecond.value = totalTimeMillisecond
45+
}
46+
47+
private fun setSingleRunIDList(singleRunIDList : ArrayList<String>) {
48+
curAppUser.singleRunIDList.value = singleRunIDList
49+
}
50+
51+
fun getUserName() : String {
52+
return curAppUser.userName
53+
}
54+
55+
fun getUserEmail() : String {
56+
return curAppUser.email
57+
}
58+
59+
fun getUserPhotoURL() : String {
60+
return curAppUser.googleAccountProfileUrl
61+
}
62+
63+
fun initAppUser(curFirebaseUser : FirebaseUser) {
64+
Log.d(PROFILE_VM_TAG, "initAppUser")
65+
curAppUser = AppUser(curFirebaseUser.displayName ?: "", curFirebaseUser.email ?: "",
66+
curFirebaseUser.uid, curFirebaseUser.photoUrl.toString())
67+
}
68+
69+
fun initAppUserStatistic() {
70+
val uid = getUid()
71+
val ref = Firebase.firestore.collection(USER_COLLECTION_NAME).document(uid)
72+
ref.get()
73+
.addOnSuccessListener {document ->
74+
Log.d(PROFILE_VM_TAG, "Get doc successfully")
75+
setTotalDistanceMeters(document.data!![KEY_TOTAL_DIS_M] as Long)
76+
setTotalEnergyCalories(document.data!![KEY_TOTAL_EN_CAL] as Long)
77+
setTotalTimeMillisecond(document.data!![KEY_TOTAL_TIME_MS] as Long)
78+
setSingleRunIDList(document.data!![KEY_SINGLE_RUN_ID_LIST] as ArrayList<String>)
79+
}
80+
.addOnFailureListener {
81+
Log.w(PROFILE_VM_TAG, "Get doc Failed")
82+
}
83+
}
84+
85+
companion object {
86+
const val PROFILE_VM_TAG = "ProfileVM"
87+
const val USER_COLLECTION_NAME = "RunUser"
88+
const val RUN_COLLECTION_NAME = "SingleRun"
89+
const val KEY_USR_NAME = "UserName"
90+
const val KEY_USR_EMAIL = "Email"
91+
const val KEY_TOTAL_DIS_M = "TotalDistanceMeters"
92+
const val KEY_TOTAL_EN_CAL = "TotalEnergyCalories"
93+
const val KEY_TOTAL_TIME_MS = "TotalTimeMillisecond"
94+
const val KEY_SINGLE_RUN_ID_LIST = "SingleRunIDList"
95+
}
96+
}
97+
98+
class DownloadImageTask(var bmImage: ImageView) :
99+
AsyncTask<String?, Void?, Bitmap?>() {
100+
101+
override fun onPostExecute(result: Bitmap?) {
102+
Log.d(DOWNLOAD_IMAGE_TASK_TAG, "onPostExecute")
103+
bmImage.setImageBitmap(result)
104+
}
105+
106+
override fun doInBackground(vararg urls: String?): Bitmap? {
107+
val userPhotoUrl = urls[0]
108+
var userPhotoBitmap: Bitmap? = null
109+
try {
110+
val inStream: InputStream = URL(userPhotoUrl).openStream()
111+
userPhotoBitmap = BitmapFactory.decodeStream(inStream)
112+
} catch (e: Exception) {
113+
Log.e(DOWNLOAD_IMAGE_TASK_TAG, e.message!!)
114+
}
115+
return userPhotoBitmap
116+
}
117+
118+
companion object {
119+
const val DOWNLOAD_IMAGE_TASK_TAG = "DownloadImageTask"
120+
}
121+
}

android/canonical/app/src/main/java/com/google/samples/quickstart/canonical/RunFragment.kt

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,17 @@ import android.view.View
88
import android.view.ViewGroup
99
import android.widget.Chronometer
1010
import androidx.databinding.DataBindingUtil
11-
import androidx.lifecycle.ViewModelProviders
11+
import androidx.fragment.app.activityViewModels
1212
import com.google.samples.quickstart.canonical.databinding.FragmentRunBinding
1313
import kotlinx.android.synthetic.main.fragment_run.*
1414

15-
// TODO: Rename parameter arguments, choose names that match
16-
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
17-
private const val ARG_PARAM1 = "param1"
18-
private const val ARG_PARAM2 = "param2"
1915

20-
/**
21-
* A simple [Fragment] subclass.
22-
* Use the [RunFragment.newInstance] factory method to
23-
* create an instance of this fragment.
24-
*/
2516
class RunFragment : Fragment() {
2617
// TODO: Rename and change types of parameters
2718
private var param1: String? = null
2819
private var param2: String? = null
2920

30-
private lateinit var stopwatchVM: StopwatchViewModel
21+
private val stopwatchVM: StopwatchViewModel by activityViewModels()
3122
private lateinit var binding : FragmentRunBinding
3223

3324
private fun startStopTimer(chronometer : Chronometer) {
@@ -43,17 +34,6 @@ class RunFragment : Fragment() {
4334
}
4435
}
4536

46-
override fun onCreate(savedInstanceState: Bundle?) {
47-
super.onCreate(savedInstanceState)
48-
arguments?.let {
49-
param1 = it.getString(ARG_PARAM1)
50-
param2 = it.getString(ARG_PARAM2)
51-
}
52-
stopwatchVM = activity?.run {
53-
ViewModelProviders.of(this)[StopwatchViewModel::class.java]
54-
} ?: throw Exception("Null Activity")
55-
}
56-
5737
override fun onCreateView(
5838
inflater: LayoutInflater, container: ViewGroup?,
5939
savedInstanceState: Bundle?
@@ -65,23 +45,7 @@ class RunFragment : Fragment() {
6545
}
6646

6747
companion object {
68-
/**
69-
* Use this factory method to create a new instance of
70-
* this fragment using the provided parameters.
71-
*
72-
* @param param1 Parameter 1.
73-
* @param param2 Parameter 2.
74-
* @return A new instance of fragment RunFragment.
75-
*/
76-
// TODO: Rename and change types and number of parameters
77-
@JvmStatic
78-
fun newInstance(param1: String, param2: String) =
79-
RunFragment().apply {
80-
arguments = Bundle().apply {
81-
putString(ARG_PARAM1, param1)
82-
putString(ARG_PARAM2, param2)
83-
}
84-
}
48+
const val RUN_FRAGMENT_TAG = "RunFragment"
8549
}
8650

8751
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

0 commit comments

Comments
 (0)