#codlab
Explore tagged Tumblr posts
jacob-cs · 5 years ago
Text
android codelab 에서 예시로 보여주는 kotlin development
2.4 data bindings
original source: https://codelabs.developers.google.com/codelabs/kotlin-android-training-data-binding-basics/index.html?index=..%2F..android-kotlin-fundamentals#5
Steps to use data binding to replace calls to findViewById():
Enable data binding in the android section of the build.gradle file: dataBinding { enabled = true }
Use <layout> as the root view in your XML layout.
Define a binding variable: private lateinit var binding: ActivityMainBinding
Create a binding object in MainActivity, replacing setContentView: binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
Replace calls to findViewById() with references to the view in the binding object. For example: findViewById<Button>(R.id.done_button) ⇒ binding.doneButton (In the example, the name of the view is generated camel case from the view's id in the XML.)
Steps for binding views to data:
Create a data class for your data.
Add a <data> block inside the <layout> tag.
Define a <variable> with a name, and a type that is the data class.
<data>   <variable       name="myName"       type="com.example.android.aboutme.MyName" /> </data>
In MainActivity, create a variable with an instance of the data class. For example: private val myName: MyName = MyName("Aleks Haecky")
In the binding object, set the variable to the variable you just created: binding.myName = myName
In the XML, set the content of the view to the variable that you defined in the <data> block. Use dot notation to access the data inside the data class. android:text="@={myName.name}"
.
.
.
.
3.1 fragment
https://codelabs.developers.google.com/codelabs/kotlin-android-training-create-and-add-fragment/index.html?index=..%2F..android-kotlin-fundamentals#3
.
.
.
.
3.2  Navigation components
original source : https://codelabs.developers.google.com/codelabs/kotlin-android-training-add-navigation/index.html?index=..%2F..android-kotlin-fundamentals#0
To use the Android navigation library, you need to do some setup:
Add dependencies for navigation-fragment-ktx and navigation-ui-ktx in the module-level build.gradle file.
Add an ext variable for the navigationVersion in the project-level build.gradle file.
Navigation destinations are fragments, activities, or other app components that the user navigates to. A navigation graph defines the possible paths from one navigation destination to the next.
To create a navigation graph, create a new Android resource file of type Navigation. This file defines the navigation flow through the app. The file is in the res/navigation folder, and it's typically called navigation.xml.
To see the navigation graph in the Navigation Editor, open the navigation.xml file and click the Design tab.
Use the Navigation Editor to add destinations such as fragments to the navigation graph.
To define the path from one destination to another, use the Navigation Graph to create an action that connects the destinations. In the navigation.xml file, each of these connections is represented as an action that has an ID.
A navigation host fragment, usually named NavHostFragment, acts as a host for the fragments in the navigation graph:
As the user moves between destinations defined in the navigation graph, the NavHostFragment swaps the fragments in and out and manages the fragment back stack.
In the activity_main.xml layout file, the NavHostFragment is represented by a fragment element with the name android:name="androidx.navigation.fragment.NavHostFragment".
To define which fragment is displayed when the user taps a view (for example a button), set the onClick listener for the view:
In the onClick listener, call findNavController().navigate() on the view.
Specify the ID of the action that leads to the destination.
Conditional navigation navigates to one screen in one case, and to a different screen in another case. To create conditional navigation:
Use the Navigation Editor to create a connection from the starting fragment to each of the possible destination fragments.
Give each connection a unique ID.
In the click-listener method for the View, add code to detect the conditions. Then call findNavController().navigate() on the view, passing in the ID for the appropriate action.
참고자료)  popup popupto PopUpToInclusive 설명 10분분량 매우 간결하다.
https://youtu.be/mLfWvSGG5c8
The Back button
The system's Back button is usually at the bottom of the device. By default, the Back button navigates the user back to the screen they viewed most recently. In some situations, you can control where the Back button takes the user:
In the Navigation Editor, you can use the Attributes pane to change an action's Pop To setting. This setting removes destinations from the back stack, which has the effect of determining where the Back button takes the user.
The Pop To setting appears as the popUpTo attribute in the navigation.xml file.
Selecting the Inclusive checkbox sets the popUpToInclusive attribute to true. All destinations up to and including this destination are removed from the back stack.
If an action's popUpTo attribute is set to the app's the starting destination and popUpToInclusive is set to true, the Back button takes the user all the way out of the app.
The Up button
Screens in an Android app can have an on-screen Up button that appears at the top left of the app bar. (The app bar is sometimes called the action bar.) The Up button navigates "upwards" within the app's screens, based on the hierarchical relationships between screens.
The navigation controller's NavigationUI library integrates with the app bar to allow the user to tap the Up button on the app bar to get back to the app's home screen from anywhere in the app.
To link the navigation controller to the app bar:
In onCreate(), call setupActionBarWithNavController() on the NavigationUI class, passing in the navigation controller:
val navController = this.findNavController(R.id.myNavHostFragment) NavigationUI.setupActionBarWithNavController(this,navController)
Override the onSupportNavigateUp() method to call navigateUp() in the navigation controller:
override fun onSupportNavigateUp(): Boolean {        val navController = this.findNavController(R.id.myNavHostFragment)        return navController.navigateUp()    } }
The options menu
The options menu is a menu that the user accesses from the app bar by tapping the icon with the three vertical dots . To create an options menu with a menu item that displays a fragment, make sure the fragment has an ID. Then define the options menu and code the onOptionsItemSelected() handler for the menu items.
1. Make sure the fragment has an ID:
Add the destination fragment to the navigation graph and note the ID of the fragment. (You can change the ID if you like.)
2. Define the options menu:
Create an Android resource file of type Menu, typically named options_menu.xml. The file is stored in the Res > Menu folder.
Open the options_menu.xml file in the design editor and drag a Menu Item widget from the Palette pane to the menu.
For convenience, make the ID of the menu item the same as the ID of the fragment to display when the user clicks this menu item. This step is not required, but it makes it easier to code the onClick behavior for the menu item.
3. Code the onClick handler for the menu item:
In the fragment or activity that displays the options menu, in onCreateView(), call setHasOptionsMenu(true) to enable the options menu.
Implement onCreateOptionsMenu() to inflate the options menu:
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {   super.onCreateOptionsMenu(menu, inflater)   inflater?.inflate(R.menu.options_menu, menu) }
Override the onOptionsItemSelected() method to take the appropriate action when the menu item is clicked. The following code displays the fragment that has the same ID as the menu item. (This code only works if the menu item and the fragment have identical ID values.)
override fun onOptionsItemSelected(item: MenuItem?): Boolean {   return NavigationUI.onNavDestinationSelected(item!!,           view!!.findNavController())           || super.onOptionsItemSelected(item) }
The navigation drawer
The navigation drawer is a panel that slides out from the edge of the screen. There are two ways for the user to open the navigation drawer:
Swipe from the starting edge (usually the left) on any screen.
Use the drawer button (three lines) on the app bar at the top of the app.
To add a navigation drawer to your app:
Add dependencies to build.gradle (app).
Make sure each destination fragment has an ID.
Create the menu for the drawer.
Add the drawer to the layout for the fragment.
Connect the drawer to the navigation controller.
Set up the drawer button in the app bar.
These steps are explained in more detail below.
1. Add dependencies to build.gradle:
The navigation drawer is part of the Material Components for Android library. Add the Material library to the build.gradle (app) file:
dependencies {    ...    implementation "com.google.android.material:material:$supportlibVersion"    ... }
2. Give each destination fragment an ID:
If a fragment is reachable from the navigation drawer, open it in the navigation graph to make sure that it has an ID.
3. Create the menu for the drawer:
Create an Android resource file of type Menu (typically called navdrawer_menu) for a navigation drawer menu. This creates a new navdrawer_menu.xml file in the Res > Menu folder.
In the design editor, add Menu Item widgets to the Menu.
4. Add the drawer to the layout for the fragment:
In the layout that contains the navigation host fragment (which is typically the main layout), use <androidx.drawerlayout.widget.DrawerLayout> as the root view.
Add a <com.google.android.material.navigation.NavigationView> view to the layout.
5. Connect the drawer to the navigation controller:
Open the activity that creates the navigation controller. (The main activity is typically the one you want here.) In onCreate(), use NavigationUI.setupWithNavController()to connect the navigation drawer with the navigation controller:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(       this, R.layout.activity_main) NavigationUI.setupWithNavController(binding.navView, navController)
6. Set up the drawer button in the app bar:
In onCreate() in the activity that creates the navigation controller (which is typically the main activity), pass the drawer layout as the third parameter to NavigationUI.setupActionBarWithNavController:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(    this, R.layout.activity_main) NavigationUI.setupActionBarWithNavController(    this, navController, binding.drawerLayout)
To make the Up button work with the drawer button, edit onSupportNavigateUp() to return NavigationUI.navigateUp(). Pass the navigation controller and the drawer layout to navigateUp().
override fun onSupportNavigateUp(): Boolean {   val navController = this.findNavController(R.id.myNavHostFragment)   return NavigationUI.navigateUp(navController, drawerLayout) }
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-start-external-activity/index.html?index=..%2F..android-kotlin-fundamentals#0
3.3
Safe Args navigation pass arguments fragment NavDirection
https://codelabs.developers.google.com/codelabs/kotlin-android-training-start-external-activity/index.html?index=..%2F..android-kotlin-fundamentals#2
내가 헷갈렸던 내용) navigation을 이용한 fragment간의 이동시 action을 통해 이동한다. 이때 args를 전달할수 있는데 그 arg는 아래와 같이 action이름.arg이름 형태로 접근 할수 있다. navigation xml화일을 보면 <action> 안에 <argument>를 가지고 있는 것을 볼수 있다. 
<fragment android:id="@+id/myFragment" >     <argument         android:name="myArg"         app:argType="integer"         android:defaultValue="0" /> </fragment>
Tumblr media
.
..
.
.
4.1 Lifecycle   onSaveInstanceState   Bundle 
https://codelabs.developers.google.com/codelabs/kotlin-android-training-complex-lifecycle/index.html?index=..%2F..android-kotlin-fundamentals#2
Lifecycle tips
If you set up or start something in a lifecycle callback, stop or remove that thing in the corresponding callback. By stopping the thing, you make sure it doesn't keep running when it's no longer needed. For example, if you set up a timer in onStart(), you need to pause or stop the timer in onStop().
Use onCreate() only to initialize the parts of your app that run once, when the app first starts. Use onStart() to start the parts of your app that run both when the app starts, and each time the app returns to the foreground.
Lifecycle library
Use the Android lifecycle library to shift lifecycle control from the activity or fragment to the actual component that needs to be lifecycle-aware.
Lifecycle owners are components that have (and thus "own") lifecycles, including Activity and Fragment. Lifecycle owners implement the LifecycleOwner interface.
Lifecycle observers pay attention to the current lifecycle state and perform tasks when the lifecycle changes. Lifecycle observers implement the LifecycleObserver interface.
Lifecycle objects contain the actual lifecycle states, and they trigger events when the lifecycle changes.
To create a lifecycle-aware class:
Implement the LifecycleObserver interface in classes that need to be lifecycle-aware.
Initialize a lifecycle observer class with the lifecycle object from the activity or fragment.
In the lifecycle observer class, annotate lifecycle-aware methods with the lifecycle state change they are interested in. For example, the @OnLifecycleEvent(Lifecycle.Event.ON_START)annotation indicates that the method is watching the onStart lifecycle event.
Process shutdowns and saving activity state
Android regulates apps running in the background so that the foreground app can run without problems. This regulation includes limiting the amount of processing that apps in the background can do, and sometimes even shutting down your entire app process.
The user cannot tell if the system has shut down an app in the background. The app still appears in the recents screen and should restart in the same state in which the user left it.
The Android Debug Bridge (adb) is a command-line tool that lets you send instructions to emulators and devices attached to your computer. You can use adb to simulate a process shutdown in your app.
When Android shuts down your app process, the onDestroy() lifecycle method is not called. The app just stops.
Preserving activity and fragment state
When your app goes into the background, just after onStop() is called, app data is saved to a bundle. Some app data, such as the contents of an EditText, is automatically saved for you.
The bundle is an instance of Bundle, which is a collection of keys and values. The keys are always strings.
Use the onSaveInstanceState() callback to save other data to the bundle that you want to retain, even if the app was automatically shut down. To put data into the bundle, use the bundle methods that start with put, such as putInt().
You can get data back out of the bundle in the onRestoreInstanceState() method, or more commonly in onCreate(). The onCreate() method has a savedInstanceState parameter that holds the bundle.
If the savedInstanceState variable contains null, the activity was started without a state bundle and there is no state data to retrieve.
To retrieve data from the bundle with a key, use the Bundle methods that start with get, such as getInt().
Configuration changes
A configuration change happens when the state of the device changes so radically that the easiest way for the system to resolve the change is to shut down and rebuild the activity.
The most common example of a configuration change is when the user rotates the device from portrait to landscape mode, or from landscape to portrait mode. A configuration change can also occur when the device language changes or a hardware keyboard is plugged in.
When a configuration change occurs, Android invokes all the activity lifecycle's shutdown callbacks. Then Android restarts the activity from scratch, running all the lifecycle startup callbacks.
When Android shuts down an app because of a configuration change, it restarts the activity with the state bundle that is available to onCreate().
As with process shutdown, save your app's state to the bundle in onSaveInstanceState().
.
.
.
.
5.1 view model factory provider에 대한 참고사항
어떤 방법이 옳고 어떻게 해야 boiler plate code를 줄일수 있는지 설명한다.
https://proandroiddev.com/view-model-creation-in-android-android-architecture-components-kotlin-ce9f6b93a46b
.
.
.
.
https://developer.android.com/topic/libraries/architecture/viewmodel
ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context
Tumblr media
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data/index.html?index=..%2F..android-kotlin-fundamentals#10
5.2 LiveData
LiveData is an observable data holder class that is lifecycle-aware, one of the Android Architecture Components.
You can use LiveData to enable your UI to update automatically when the data updates.
LiveData is observable, which means that an observer like an activity or an fragment can be notified when the data held by the LiveData object changes.
LiveData holds data; it is a wrapper that can be used with any data.
LiveData is lifecycle-aware, meaning that it only updates observers that are in an active lifecycle state such as STARTED or RESUMED.
To add LiveData
Change the type of the data variables in ViewModel to LiveData or MutableLiveData.
MutableLiveData is a LiveData object whose value can be changed. MutableLiveData is a generic class, so you need to specify the type of data that it holds.
To change the value of the data held by the LiveData, use the setValue() method on the LiveData variable.
To encapsulate LiveData
The LiveData inside the ViewModel should be editable. Outside the ViewModel, the LiveData should be readable. This can be implemented using a Kotlin backing property.
A Kotlin backing property allows you to return something from a getter other than the exact object.
To encapsulate the LiveData, use private MutableLiveData inside the ViewModel and return a LiveData backing property outside the ViewModel.
Observable LiveData
LiveData follows an observer pattern. The "observable" is the LiveData object, and the observers are the methods in the UI controllers, like fragments. Whenever the data wrapped inside LiveData changes, the observer methods in the UI controllers are notified.
To make the LiveData observable, attach an observer object to the LiveData reference in the observers (such as activities and fragments) using the observe() method.
This LiveData observer pattern can be used to communicate from the ViewModel to the UI controllers.
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data-data-binding/index.html?index=..%2F..android-kotlin-fundamentals#6
5.3 Data binding and LiveData
Summary
The Data Binding Library works seamlessly with Android Architecture Components like ViewModel and LiveData.
The layouts in your app can bind to the data in the Architecture Components, which already help you manage the UI controller's lifecycle and notify about changes in the data.
ViewModel data binding
You can associate a ViewModel with a layout by using data binding.
ViewModel objects hold the UI data. By passing ViewModel objects into the data binding, you can automate some of the communication between the views and the ViewModel objects.
How to associate a ViewModel with a layout:
In the layout file, add a data-binding variable of the type ViewModel.
  <data>       <variable           name="gameViewModel"           type="com.example.android.guesstheword.screens.game.GameViewModel" />   </data>
In the GameFragment file, pass the GameViewModel into the data binding.
binding.gameViewModel = viewModel
Listener bindings
Listener bindings are binding expressions in the layout that run when click events such as onClick() are triggered.
Listener bindings are written as lambda expressions.
Using listener bindings, you replace the click listeners in the UI controllers with listener bindings in the layout file.
Data binding creates a listener and sets the listener on the view.
android:onClick="@{() -> gameViewModel.onSkip()}"
Adding LiveData to data binding
LiveData objects can be used as a data-binding source to automatically notify the UI about changes in the data.
You can bind the view directly to the LiveData object in the ViewModel. When the LiveData in the ViewModel changes, the views in the layout can be automatically updated, without the observer methods in the UI controllers.
android:text="@{gameViewModel.word}"
To make the LiveData data binding work, set the current activity (the UI controller) as the lifecycle owner of the binding variable in the UI controller.
binding.lifecycleOwner = this
String formatting with data binding
Using data binding, you can format a string resource with placeholders like %s for strings and %d for integers.
To update the text attribute of the view, pass in the LiveData object as an argument to the formatting string.
android:text="@{@string/quote_format(gameViewModel.word)}"
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data-transformations/index.html?index=..%2F..android-kotlin-fundamentals#7
livedata를 일반 data를 wrapper로 감싼 형태 가정하고 그안의 내용만을 수정한 livedata를 얻고자 하는 경우 Transformations를 이용해서 내용을 변경할수 있다.
5.4 LiveData and Transformations
Transforming LiveData
Sometimes you want to transform the results of LiveData. For example, you might want to format a Date string as "hours:mins:seconds," or return the number of items in a list rather than returning the list itself. To perform transformations on LiveData, use helper methods in the Transformations class.
The Transformations.map() method provides an easy way to perform data manipulations on the LiveData and return another LiveData object. The recommended practice is to put data-formatting logic that uses the Transformations class in the ViewModel along with the UI data.
Displaying the result of a transformation in a TextView
Make sure the source data is defined as LiveData in the ViewModel.
Define a variable, for example newResult. Use Transformation.map() to perform the transformation and return the result to the variable.
val newResult = Transformations.map(someLiveData) { input ->   // Do some transformation on the input live data   // and return the new value }
Make sure the layout file that contains the TextView declares a <data> variable for the ViewModel.
<data>   <variable       name="MyViewModel"       type="com.example.android.something.MyViewModel" /> </data>
In the layout file, set the text attribute of the TextView to the binding of the newResult of the ViewModel. For example:
android:text="@{SomeViewModel.newResult}"
Formatting dates
The DateUtils.formatElapsedTime() utility method takes a long number of milliseconds and formats the number to use a MM:SS string format.
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-room-database/index.html?index=..%2F..android-kotlin-fundamentals#8
6.1  Room
참고자료) official docs 
https://developer.android.com/training/data-storage/room/accessing-data
Define your tables as data classes annotated with @Entity. Define properties annotated with @ColumnInfo as columns in the tables.
Define a data access object (DAO) as an interface annotated with @Dao. The DAO maps Kotlin functions to database queries.
Use annotations to define @Insert, @Delete, and @Update functions.
Use the @Query annotation with an SQLite query string as a parameter for any other queries.
Create an abstract class that has a getInstance() function that returns a database.
Use instrumented tests to test that your database and DAO are working as expected. You can use the provided tests as a template.
.
.
.
.
일반적으로 Coroutines를 이용한 database작업의 패턴
https://codelabs.developers.google.com/codelabs/kotlin-android-training-coroutines-and-room/index.html?index=..%2F..android-kotlin-fundamentals#5
Launch a coroutine that runs on the main or UI thread, because the result affects the UI.
Call a suspend function to do the long-running work, so that you don't block the UI thread while waiting for the result.
The long-running work has nothing to do with the UI. Switch to the I/O context, so that the work can run in a thread pool that's optimized and set aside for these kinds of operations.
Then call the database function to do the work.
The pattern is shown below.
fun someWorkNeedsToBeDone {   uiScope.launch {        suspendFunction()   } } suspend fun suspendFunction() {   withContext(Dispatchers.IO) {       longrunningWork()   } }
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-coroutines-and-room/index.html?index=..%2F..android-kotlin-fundamentals#7
6.2 Corouitnes과 room database, LiveData
Use ViewModel, ViewModelFactory, and data binding to set up the UI architecture for the app.
To keep the UI running smoothly, use coroutines for long-running tasks, such as all database operations.
Coroutines are asynchronous and non-blocking. They use suspend functions to make asynchronous code sequential.
When a coroutine calls a function marked with suspend, instead of blocking until that function returns like a normal function call, it suspends execution until the result is ready. Then it resumes where it left off with the result.
The difference between blocking and suspending is that if a thread is blocked, no other work happens. If the thread is suspended, other work happens until the result is available.
To launch a coroutine, you need a job, a dispatcher, and a scope:
Basically, a job is anything that can be canceled. Every coroutine has a job, and you can use a job to cancel a coroutine.
The dispatcher sends off coroutines to run on various threads. Dispatcher.Main runs tasks on the main thread, and Dispartcher.IO is for offloading blocking I/O tasks to a shared pool of threads.
The scope combines information, including a job and dispatcher, to define the context in which the coroutine runs. Scopes keep track of coroutines.
To implement click handlers that trigger database operations, follow this pattern:
Launch a coroutine that runs on the main or UI thread, because the result affects the UI.
Call a suspend function to do the long-running work, so that you don't block the UI thread while waiting for the result.
The long-running work has nothing to do with the UI, so switch to the I/O context. That way, the work can run in a thread pool that's optimized and set aside for these kinds of operations.
Then call the database function to do the work.
Use a Transformations map to create a string from a LiveData object every time the object changes.
.
.
.
.
6.3 LiveData 와 control buttons
https://codelabs.developers.google.com/codelabs/kotlin-android-training-quality-and-states/index.html?index=..%2F..android-kotlin-fundamentals#6
Create a ViewModel and a ViewModelFactory and set up a data source.
Trigger navigation. To separate concerns, put the click handler in the view model and the navigation in the fragment.
Use encapsulation with LiveData to track and respond to state changes.
Use transformations with LiveData.
Create a singleton database.
Set up coroutines for database operations.
Triggering navigation
You define possible navigation paths between fragments in a navigation file. There are some different ways to trigger navigation from one fragment to the next. These include:
Define onClick handlers to trigger navigation to a destination fragment.
Alternatively, to enable navigation from one fragment to the next:
Define a LiveData value to record if navigation should occur.
Attach an observer to that LiveData value.
Your code then changes that value whenever navigation needs to be triggered or is complete.
Setting the android:enabled attribute
The android:enabled attribute is defined in TextView and inherited by all subclasses, including Button.
The android:enabled attribute determines whether or not a View is enabled. The meaning of "enabled" varies by subclass. For example, a non-enabled EditText prevents the user from editing the contained text, and a non-enabled Button prevents the user from tapping the button.
The enabled attribute is not the same as the visibility attribute.
You can use transformation maps to set the value of the enabled attribute of buttons based on the state of another object or variable.
Other points covered in this codelab:
To trigger notifications to the user, you can use the same technique as you use to trigger navigation.
You can use a Snackbar to notify the user.
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-recyclerview-fundamentals/index.html?index=..%2F..android-kotlin-fundamentals#7
7.1  recyclerView
Displaying a list or grid of data is one of the most common UI tasks in Android. RecyclerView is designed to be efficient even when displaying extremely large lists.
RecyclerView does only the work necessary to process or draw items that are currently visible on the screen.
When an item scrolls off the screen, its views are recycled. That means the item is filled with new content that scrolls onto the screen.
The adapter pattern in software engineering helps an object work together with another API. RecyclerView uses an adapter to transform app data into something it can display, without the need for changing how the app stores and processes data.
To display your data in a RecyclerView, you need the following parts:
RecyclerView To create an instance of RecyclerView, define a <RecyclerView> element in the layout file.
LayoutManager A RecyclerView uses a LayoutManager to organize the layout of the items in the RecyclerView, such as laying them out in a grid or in a linear list. In the <RecyclerView> in the layout file, set the app:layoutManager attribute to the layout manager (such as LinearLayoutManager or GridLayoutManager). You can also set the LayoutManager for a RecyclerView programmatically. (This technique is covered in a later codelab.)
Layout for each item Create a layout for one item of data in an XML layout file.
Adapter Create an adapter that prepares the data and how it will be displayed in a ViewHolder. Associate the adapter with the RecyclerView. When RecyclerView runs, it will use the adapter to figure out how to display the data on the screen. The adapter requires you to implement the following methods: – getItemCount() to return the number of items. – onCreateViewHolder() to return the ViewHolder for an item in the list. – onBindViewHolder() to adapt the data to the views for an item in the list.
ViewHolder A ViewHolder contains the view information for displaying one item from the item's layout.
The onBindViewHolder() method in the adapter adapts the data to the views. You always override this method. Typically, onBindViewHolder() inflates the layout for an item, and puts the data in the views in the layout.
Because the RecyclerView knows nothing about the data, the Adapter needs to inform the RecyclerView when that data changes. Use notifyDataSetChanged()to notify the Adapter that the data has changed.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-recyclerview-fundamentals/index.html?index=..%2F..android-kotlin-fundamentals#5
refactor refactoring 
특정코드 외부로 extract 해서 새로 function만들기
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#2
7.2  DiffUtil , data binding, ListAdapter
https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#3
DiffUtil의 사용예시 (ListAdapter에서 변경된 data에 해당하는 viewholder만 변경해서 효율을 높이기 위해 사용된다. 어떤 data가 변경된 것인지 확인하는 역할을 한다.)
https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#4
ListAdapter 사용 예시. 
ListAdapter를 RecyclerView 사용하면 몇몇 편해지는 부분이 있다. getItemCount()를 구현하지 않아도 된다. 보여줄 data list를 adapter에 전달할때는 adapter.submitList(it)를 이용한다. adapter안에 data list를 저장할 variable을 만들 필요가 없다. 
https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#5
data binding 과 recycler view를 같이 사용하는 예시
refactor refactoring 하는 예시 몇개 있음
https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#6
data binding 과 recycler view를 같이 사용하는 예시
DiffUtil:
RecyclerView has a class called DiffUtil which is for calculating the differences between two lists.
DiffUtil has a class called ItemCallBack that you extend in order to figure out the difference between two lists.
In the ItemCallback class, you must override the areItemsTheSame() and areContentsTheSame() methods.
ListAdapter:
To get some list management for free, you can use the ListAdapter class instead of RecyclerView.Adapter. However, if you use ListAdapter you have to write your own adapter for other layouts, which is why this codelab shows you how to do it.
To open the intention menu in Android Studio, place the cursor on any item of code and press Alt+Enter (Option+Enter on a Mac). This menu is particularly helpful for refactoring code and creating stubs for implementing methods. The menu is context-sensitive, so you need to place cursor exactly to get the correct menu.
Data binding:
Use data binding in the item layout to bind data to the views.
Binding adapters:
You previously used Transformations to create strings from data. If you need to bind data of different or complex types, provide binding adapters to help data binding use them.
To declare a binding adapter, define a method that takes an item and a view, and annotate the method with @BindingAdapter. In Kotlin, you can write the binding adapter as an extension function on the View. Pass in the name of the property that the adapter adapts. For example:
@BindingAdapter("sleepDurationFormatted")
In the XML layout, set an app property with the same name as the binding adapter. Pass in a variable with the data. For example:
.app:sleepDurationFormatted="@{sleep}"
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-grid-layout/index.html?index=..%2F..android-kotlin-fundamentals#5
GridLayout and RecyclerView
Layout managers manage how the items in the RecyclerView are arranged.
RecyclerView comes with out-of-the-box layout managers for common use cases such as LinearLayout for horizontal and vertical lists, and GridLayout for grids.
For more complicated use cases, implement a custom LayoutManager.
From a design perspective, GridLayout is best used for lists of items that can be represented as icons or images.
GridLayout arranges items in a grid of rows and columns. Assuming vertical scrolling, each item in a row takes up what's called a "span."
You can customize how many spans an item takes up, creating more interesting grids without the need for a custom layout manager.
Create an item layout for one item in the grid, and the layout manager takes care of arranging the items.
You can set the LayoutManager for the RecyclerView either in the XML layout file that contains the <RecyclerView> element, or programmatically.
.
.
.
.
https://codelabs.developers.google.com/codelabs/kotlin-android-training-interacting-with-items/index.html?index=..%2F..android-kotlin-fundamentals#6
interaction with recyclerview
To make items in a RecyclerView respond to clicks, attach click listeners to list items in the ViewHolder, and handle clicks in the ViewModel.
To make items in a RecyclerView respond to clicks, you need to do the following:
Create a listener class that takes a lambda and assigns it to an onClick() function.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {   fun onClick(night: SleepNight) = clickListener(night.nightId) }
Set the click listener on the view.
android:onClick="@{() -> clickListener.onClick(sleep)}"
Pass the click listener to the adapter constructor, into the view holder, and add it to the binding object.
class SleepNightAdapter(val clickListener: SleepNightListener):       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
In the fragment that shows the recycler view, where you create the adapter, define a click listener by passing a lambda to the adapter.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->      sleepTrackerViewModel.onSleepNightClicked(nightId) })
Implement the click handler in the view model. For clicks on list items, this commonly triggers navigation to a detail fragment.
.
.
package com.example.android.trackmysleepquality.sleeptracker import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.databinding.ListItemSleepNightBinding class SleepNightAdapter(val clickListener: SleepNightListener) : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {    override fun onBindViewHolder(holder: ViewHolder, position: Int) {        holder.bind(getItem(position)!!, clickListener)    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {        return ViewHolder.from(parent)    }    class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){        fun bind(item: SleepNight, clickListener: SleepNightListener) {            binding.sleep = item            binding.clickListener = clickListener            binding.executePendingBindings()        }        companion object {            fun from(parent: ViewGroup): ViewHolder {                val layoutInflater = LayoutInflater.from(parent.context)                val binding = ListItemSleepNightBinding.inflate(layoutInflater, parent, false)                return ViewHolder(binding)            }        }    } } class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {    override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {        return oldItem.nightId == newItem.nightId    }    override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {        return oldItem == newItem    } } class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {    fun onClick(night: SleepNight) = clickListener(night.nightId) }
<layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools">    <data>        <variable            name="sleep"            type="com.example.android.trackmysleepquality.database.SleepNight"/>        <variable            name="clickListener"            type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:onClick="@{() -> clickListener.onClick(sleep)}">        <ImageView            android:id="@+id/quality_image"            android:layout_width="@dimen/icon_size"            android:layout_height="60dp"            android:layout_marginStart="16dp"            android:layout_marginTop="8dp"            android:layout_marginBottom="8dp"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            tools:srcCompat="@drawable/ic_sleep_5"            app:sleepImage="@{sleep}"/>        <TextView            android:id="@+id/quality_string"            android:layout_width="0dp"            android:layout_height="20dp"            android:textAlignment="center"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="@+id/quality_image"            app:layout_constraintHorizontal_bias="0.0"            app:layout_constraintStart_toStartOf="@+id/quality_image"            app:layout_constraintTop_toBottomOf="@+id/quality_image"            tools:text="Excellent!!!"            app:sleepQualityString="@{sleep}" />    </androidx.constraintlayout.widget.ConstraintLayout> </layout>
0 notes