From f35f608c0b36dd0a0100a48ebbcce3dce5b63a4c Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 15:18:39 +0530 Subject: [PATCH 01/10] Removed unused layout file. --- .../layout/chrono_activity_databinding.xml | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 app/src/main/res/layout/chrono_activity_databinding.xml diff --git a/app/src/main/res/layout/chrono_activity_databinding.xml b/app/src/main/res/layout/chrono_activity_databinding.xml deleted file mode 100644 index 83b4db6..0000000 --- a/app/src/main/res/layout/chrono_activity_databinding.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - From 544d1324e5e8e3ea7acac12fa08a45b415ffaa28 Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 15:19:04 +0530 Subject: [PATCH 02/10] Added notes. --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 9fadfe6..56eed82 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,26 @@ Please follow along the codelab steps [here](https://codelabs.developers.google. # Filing issues If you find errors in the codelab steps or the code, please file them [here](https://github.com/googlecodelabs/android-lifecycles/issues/new) + +## Notes + +1. An activity is a poor choice to manage app data. + Activities and fragments are short-lived objects which are created and destroyed frequently as a user interacts with an app. + +2. ViewModel can retain data across the entire lifecycle of an activity or a fragment. + A ViewModel is also better suited to managing tasks related to network communication, as well as data manipulation and persistence. + To avoid memory leaks, the ViewModel doesn't include references to the activity. + +3. Instead of modifying views directly from the ViewModel, you configure an activity or fragment to observe a data source, receiving the data when it changes. + This arrangement is called the observer pattern. + +4. LiveData is a special observable class which is lifecycle-aware, and only notifies active observers. + To make change value of MutableLiveData: setValue() method must be called from the main thread. + To change from inside a background thread, you can use postValue(). + +5. LifecycleOwner is an interface that is used by any class that has an Android lifecycle. Practically, it means that the class has a Lifecycle object that represents its lifecycle. + +6. ADB commands: + + ./adb shell ps -A |grep lifecycle + ./adb shell am kill com.example.android.codelabs.lifecycle From 6da6f4ad2967111c2b7f4b35cdd46994d5d8ca33 Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 15:20:23 +0530 Subject: [PATCH 03/10] Updated Gradle Build Tools and Plugin version --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 77c8f13..ad16d90 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:4.2.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cbc4ca0..43dfce9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-rc-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip From a533a5fcf6560b33a01cf3d2b86ea15e79b114ea Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 15:52:05 +0530 Subject: [PATCH 04/10] Added notes. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 56eed82..fed1712 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,8 @@ If you find errors in the codelab steps or the code, please file them [here](htt ./adb shell ps -A |grep lifecycle ./adb shell am kill com.example.android.codelabs.lifecycle + +7. Some UI elements, including EditText, save their state using their own onSaveInstanceState implementation. + This state is restored after a process is killed the same way it's restored after a configuration change. + +8. Use SavedStateHandle to save ViewModel properties during process kill. From 918f9455a7edb2f94fd6fa4aa9d37acc7f992d31 Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 18:21:53 +0530 Subject: [PATCH 05/10] Added Custom LIfeCycleOwner usecase with notes. --- README.md | 89 ++++++++++- .../BoundLocationManager.java | 83 ++++++++++ .../LocationActivity.java | 143 ++++++++++++++++++ 3 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/BoundLocationManager.java create mode 100644 app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/LocationActivity.java diff --git a/README.md b/README.md index fed1712..338b3a3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,94 @@ If you find errors in the codelab steps or the code, please file them [here](htt To make change value of MutableLiveData: setValue() method must be called from the main thread. To change from inside a background thread, you can use postValue(). -5. LifecycleOwner is an interface that is used by any class that has an Android lifecycle. Practically, it means that the class has a Lifecycle object that represents its lifecycle. +5. Lifecycle-aware components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments. + The androidx.lifecycle package provides classes and interfaces that let you build lifecycle-aware componentsLifecycleOwner is an interface that is used by any class that has an Android lifecycle. + + ***Lifecycle*** is a class that holds the information about the lifecycle state of a component (like an activity or a fragment) and allows other objects to observe this state. + Lifecycle uses two main enumerations to track the lifecycle status for its associated component: + + ***Event*** + The lifecycle events that are dispatched from the framework and the Lifecycle class. These events map to the callback events in activities and fragments. + ***State*** + The current state of the component tracked by the Lifecycle object. + + **Think of the states as nodes of a graph and events as the edges between these nodes** + + [![LifeCycle State up and down events ](https://developer.android.com/images/topic/libraries/architecture/lifecycle-states.svg)] + + ``` + static State getStateAfter(Event event) { + switch (event) { + case ON_CREATE: + case ON_STOP: + return CREATED; + case ON_START: + case ON_PAUSE: + return STARTED; + case ON_RESUME: + return RESUMED; + case ON_DESTROY: + return DESTROYED; + case ON_ANY: + break; + } + throw new IllegalArgumentException("Unexpected event value " + event); + } + + private static Event downEvent(State state) { + switch (state) { + case INITIALIZED: + throw new IllegalArgumentException(); + case CREATED: + return ON_DESTROY; + case STARTED: + return ON_STOP; + case RESUMED: + return ON_PAUSE; + case DESTROYED: + throw new IllegalArgumentException(); + } + throw new IllegalArgumentException("Unexpected state value " + state); + } + + private static Event upEvent(State state) { + switch (state) { + case INITIALIZED: + case DESTROYED: + return ON_CREATE; + case CREATED: + return ON_START; + case STARTED: + return ON_RESUME; + case RESUMED: + throw new IllegalArgumentException(); + } + throw new IllegalArgumentException("Unexpected state value " + state); + } + ``` + + A class can monitor the component's lifecycle status by adding annotations to its methods. Then you can add an observer by calling the addObserver() method of the Lifecycle class and passing an instance of your observer, as shown in the following example: + + + ``` + public class MyObserver implements LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void connectListener() { + ... + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + public void disconnectListener() { + ... + } + } + + myLifecycleOwner.getLifecycle().addObserver(new MyObserver()); + ``` + + ***LifecycleOwner*** is a single method interface that denotes that the class has a Lifecycle. It has one method, getLifecycle(), which must be implemented by the class + + Components that implement LifecycleObserver work seamlessly with components that implement LifecycleOwner because an owner can provide a lifecycle, which an observer can register to watch. 6. ADB commands: diff --git a/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/BoundLocationManager.java b/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/BoundLocationManager.java new file mode 100644 index 0000000..72561b2 --- /dev/null +++ b/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/BoundLocationManager.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.lifecycles.step7_lifeCycleRegistry; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.util.Log; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.OnLifecycleEvent; + +public class BoundLocationManager { + public static void bindLocationListenerIn(LifecycleOwner lifecycleOwner, + LocationListener listener, Context context) { + new BoundLocationListener(lifecycleOwner, listener, context); + } + + @SuppressWarnings("MissingPermission") + static class BoundLocationListener implements LifecycleObserver { + private final Context mContext; + private LocationManager mLocationManager; + private final LocationListener mListener; + + public BoundLocationListener(LifecycleOwner lifecycleOwner, + LocationListener listener, Context context) { + mContext = context; + mListener = listener; + lifecycleOwner.getLifecycle().addObserver(this); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + void addLocationListener() { + // Note: Use the Fused Location Provider from Google Play Services instead. + // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi + + mLocationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener); + Log.d("BoundLocationMgr", "Listener added"); + + // Force an update with the last location, if available. + Location lastLocation = mLocationManager.getLastKnownLocation( + LocationManager.GPS_PROVIDER); + if (lastLocation != null) { + mListener.onLocationChanged(lastLocation); + }else { + Location zero=new Location("zero"); + zero.setLatitude(0.0); + zero.setLongitude(0.0); + mListener.onLocationChanged(zero); + } + } + + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + void removeLocationListener() { + if (mLocationManager == null) { + return; + } + mLocationManager.removeUpdates(mListener); + mLocationManager = null; + Log.d("BoundLocationMgr", "Listener removed"); + } + } +} diff --git a/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/LocationActivity.java b/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/LocationActivity.java new file mode 100644 index 0000000..36f7d5a --- /dev/null +++ b/app/src/main/java/com/example/android/lifecycles/step7_lifeCycleRegistry/LocationActivity.java @@ -0,0 +1,143 @@ +/* + * Copyright 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.lifecycles.step7_lifeCycleRegistry; + +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationListener; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; + +import com.example.android.codelabs.lifecycle.R; + +public class LocationActivity extends Activity implements LifecycleOwner { + + private static final int REQUEST_LOCATION_PERMISSION_CODE = 1; + + private LocationListener mGpsListener = new MyLocationListener(); + + LifecycleRegistry lifecycleRegistry; + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED + && grantResults[1] == PackageManager.PERMISSION_GRANTED) { + bindLocationListener(); + } else { + Toast.makeText(this, "This sample requires Location access", Toast.LENGTH_LONG).show(); + } + } + + private void bindLocationListener() { + BoundLocationManager.bindLocationListenerIn(this, mGpsListener, getApplicationContext()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.location_activity); + + + lifecycleRegistry=new LifecycleRegistry(this); + lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, + Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}, + REQUEST_LOCATION_PERMISSION_CODE); + } else { + bindLocationListener(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); + } @Override + + protected void onPause() { + super.onPause(); + + lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); + } + + + @Override + protected void onStop() { + super.onStop(); + + lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); + } + + + @Override + protected void onStart() { + super.onStart(); + + lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return lifecycleRegistry; + } + + private class MyLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + TextView textView = findViewById(R.id.location); + textView.setText(location.getLatitude() + ", " + location.getLongitude()); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + Toast.makeText(LocationActivity.this, + "Provider enabled: " + provider, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onProviderDisabled(String provider) { + } + } +} From 1d79dd99922dcbc77ea83f83ae8d593e71f55646 Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 18:26:37 +0530 Subject: [PATCH 06/10] Declaring activity in manifest for Custom LifeCycleOwner usecase. --- app/src/main/AndroidManifest.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 27a64dd..0542657 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -100,6 +100,14 @@ + + + + + + From 73cb240153f2c61c2f703df1f74d0b0206a58592 Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 18:27:29 +0530 Subject: [PATCH 07/10] Corrected the activity reference. --- app/src/main/res/layout/saved_state_activity.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/saved_state_activity.xml b/app/src/main/res/layout/saved_state_activity.xml index 6007b99..7d48838 100644 --- a/app/src/main/res/layout/saved_state_activity.xml +++ b/app/src/main/res/layout/saved_state_activity.xml @@ -22,7 +22,7 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context="com.example.android.lifecycles.step6.SavedStateActivity"> Date: Tue, 22 Jun 2021 18:31:56 +0530 Subject: [PATCH 08/10] Corrected the markdown image syntax. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 338b3a3..2e9d72f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you find errors in the codelab steps or the code, please file them [here](htt **Think of the states as nodes of a graph and events as the edges between these nodes** - [![LifeCycle State up and down events ](https://developer.android.com/images/topic/libraries/architecture/lifecycle-states.svg)] + ![LifeCycle State up and down events ](https://developer.android.com/images/topic/libraries/architecture/lifecycle-states.svg) ``` static State getStateAfter(Event event) { From 9d0770daac25950783faf90518cee086b12ee87b Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 18:43:57 +0530 Subject: [PATCH 09/10] Corrected the markdown code syntax. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9d72f..b2490f7 100644 --- a/README.md +++ b/README.md @@ -86,12 +86,13 @@ If you find errors in the codelab steps or the code, please file them [here](htt } throw new IllegalArgumentException("Unexpected state value " + state); } + ``` A class can monitor the component's lifecycle status by adding annotations to its methods. Then you can add an observer by calling the addObserver() method of the Lifecycle class and passing an instance of your observer, as shown in the following example: - ``` + public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void connectListener() { @@ -105,6 +106,7 @@ If you find errors in the codelab steps or the code, please file them [here](htt } myLifecycleOwner.getLifecycle().addObserver(new MyObserver()); + ``` ***LifecycleOwner*** is a single method interface that denotes that the class has a Lifecycle. It has one method, getLifecycle(), which must be implemented by the class From df01b6592bdeee67d340ba1453496c2cc4da47df Mon Sep 17 00:00:00 2001 From: Inder Bagga Date: Tue, 22 Jun 2021 18:45:44 +0530 Subject: [PATCH 10/10] Corrected the markdown code syntax. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2490f7..059fa55 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ If you find errors in the codelab steps or the code, please file them [here](htt throw new IllegalArgumentException("Unexpected state value " + state); } - ``` + ``` A class can monitor the component's lifecycle status by adding annotations to its methods. Then you can add an observer by calling the addObserver() method of the Lifecycle class and passing an instance of your observer, as shown in the following example: