constructor

https://stackoverflow.com/a/48612819/3151712

class TextViewLight : TextView {

constructor(context: Context) : super(context){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

constructor(context: Context, attrs : AttributeSet) : super(context,attrs){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

constructor(context: Context,  attrs: AttributeSet , defStyleAttr : Int) : super(context, attrs, defStyleAttr){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

}

.

.

.

constructor

https://stackoverflow.com/a/56542516/3151712

There are several ways to override your constructors,

When you need default behavior

class MyWebView(context: Context): WebView(context) {
    // code
}

When you need multiple version

class MyWebView(context: Context, attr: AttributeSet? = null): WebView(context, attr) {
    // code
}

When you need to use params inside

class MyWebView(private val context: Context): WebView(context) {
    // you can access context here
}

When you want cleaner code for better readability

class MyWebView: WebView {

    constructor(context: Context): super(context) {
        mContext = context
        setup()
    }

    constructor(context: Context, attr: AttributeSet? = null): super(context, attr) {
        mContext = context
        setup()
    }
}

.

.

.

constructor

https://stackoverflow.com/a/29168553/3151712

Kotlin supports multiple constructors since M11 which was released 19.03.2015. The syntax is as follows:

class MyView : View {
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        // ...
    }
 
    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {}
}

More info here and here.

Edit: you can also use @JvmOverloads annotation so that Kotlin auto-generates the required constructors for you:

class MyView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyle: Int = 0
) : View(context, attrs, defStyle)

Beware, though, as this approach may sometimes lead to the unexpected results, depending on how the class you inherit from defines its constructors. Good explanation of what might happen is given in that article.

.

.

.

https://stackoverflow.com/a/57102685/3151712

  1. (Not in Android Studio) Make a copy / duplicate the existing ‘OldProject’ directory.
  2. (Not in Android Studio) Rename the copied directory to ‘NewProject’.
  3. Start Android Studio 3.4.
  4. Select ‘Open an existing Android Studio project’.
  5. Navigate to the ‘NewProject’ directory, select it and click ‘Open’.
  6. Build -> Clean project.
  7. File -> Sync project with gradle file.
  8. Click the ‘1:Project’ side tab and choose Android from the drop-down menu.
  9. Expand the app -> java folder.
  10. Right-click com.example.android.oldproject and select Refactor -> Rename.
  11. Give a new name to the new project in the Rename dialog.
  12. Select ‘Search in comments and strings’ and ‘Search for text occurrences’. Click Refactor.
  13. The ‘Find: Refactoring Preview’ pane appears, click ‘Do Refactor’.
  14. Expand the res -> values folder and double-click the strings.xml file.
  15. Change the app_name string value to “New Project”.

Check the file and sync the project with the Gradle file:

  1. In Android Studio Project pane, expand Gradle Scripts and open build.gradle (Module: app).
  2. In android -> defaultConfig -> applicationId check that the value of the applicationID key is “com.example.android.newproject”. If the value isn’t correct, change it manually.
  3. File -> Sync project with gradle file

Make sure that the app and package names are correct in the AndroidManifest.xml file:

  1. Expand the app -> manifests folder and double click AndroidManifest.xml.
  2. Check that the ‘package’ name is correct. It should be “com.example.android.newproject”. If not, change it manually.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

위 작업후에도 firebase를 사용한 경우

https://stackoverflow.com/a/34923311/3151712

You have added the Google Play Services plugin to the project, which reads from the google-services.json found in your app module.

The google-services.json contains service configuration data, such as Google Project ID, application package name, etc..

Since the application package name is also stored in that json, it will not match anymore, so you have to create a new application in your Firebase console, and export the new configuration json.

Then replace the google-services.json in your project with the one you have generated.

위 작업을 해주어야 한다. 

.

.

우선 firebase 페이지에가서 새로운 프로젝트를 만든다.

그리고 setting페이지 하단을 보면 아래와 같은에 여기서 android icon 선택

image

google-services.json만드는 과정

image
image

gradle화일 둘다 수정

아래) android studio 왼쪽편에 있는 화일매니저에서 android 를  project 로 바꾸고 app안에 google-services.json이 있는 것을 확인할수 있다.

새로 생성된 화일 붙여넣기

image
image
image

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

이렇게 하고도 navigation args관련해서 navigation화일에 들어가 각각 argument의 class를 새로 고쳐주어야 했다. 또 몇몇 에러나는 부분을 손수 수정해줘야 했다.(extension func 도 그 한예에 속한다.)

=====================================================

git을 사용하는 경우 기존의 git을 없애고 새로 시작해야한다.

https://stackoverflow.com/a/31991358

In order to drop the current git repository and create a new one you need to:

  • Go to the project’s directory: cd PROJECT_DIRECTORY
  • Remove all the git specific files: rm -rf $(find . -name ".git*")
  • Initialize a new git repository: git init

This doesn’t remove your project’s files, only git configs

참고로 macos에서 숨겨진 화일도 보고 싶은경우

command + shift + . 을 하면 된다.

https://medium.com/@shashankmohabia/android-paging-efficient-way-to-populate-recycler-view-dynamically-31f39f35cdf9

처음 개념 잡기 쉽게 자세히 설명이 되어 있다.

Paging Library Architecture:

image

The paging library consists of 5 components:

  • PagedList — The Paging Library’s key component is the PagedList class, which is a collection that loads chunks of your app’s data, or pages, asynchronously. It can be used to load data from sources you define, and present it easily in your UI with a RecyclerView. As more data is needed, it’s paged into the existing PagedList object. If any loaded data changes, a new instance of PagedList is emitted to the observable data holder from a LiveData or RxJava2-based object. As PagedList objects are generated, your app’s UI presents their contents, all while respecting your UI controllers’ lifecycles.
  • DataSource and DataSource.Factory — a DataSource is the base class for loading snapshots of data into a PagedList. A DataSource.Factory is responsible for creating a DataSource.
  • LivePagedListBuilder — builds a LiveData<PagedList>, based on DataSource.Factory and a PagedList.Config(the configuration file for paging).
  • BoundaryCallback — signals when a PagedList has reached the end of available data so that more data can be fetched from the server in case of DB backed up by the server.
  • PagedListAdapter — a RecyclerView.Adapter that presents paged data from PagedLists in a RecyclerView. PagedListAdapter listens to PagedList loading callbacks as pages are loaded and uses DiffUtil to compute fine-grained updates as new PagedLists are received.

Implementation:

Step 1. Load data in chunks with the PagedList:

To start using paging, replace all the List data to PagedList in the ViewModels and its observers in activities and fragments. If there are any models responsible for updating UI, change them also.

val dataList: LiveData<List<data_model_object>> = ...
val dataList: LiveData<PagedList<data_model_object>> = ...
viewModel.dataList.observe(this, Observer<PagedList<data_model_object>> {...})

Step 2. Define the source of data for the paged list:

The PagedList loads content dynamically from a source. If the database is the main source of truth for the UI, it also represents the source for the PagedList. If your app gets data directly from the network and displays it without caching, then the class that makes network requests would be your data source.

A source is defined by a DataSource class. To page in data from a source that can change—such as a source that allows inserting, deleting or updating data—you will also need to implement a DataSource.Factory that knows how to create the DataSource. Whenever the data is updated, the DataSource is invalidated and re-created automatically through the DataSource.Factory.

The Room persistence library provides native support for data sources associated with the Paging library. For a given query, Room allows you to return a DataSource.Factory from the DAO and handles the implementation of the DataSource for you.

@Daointerface RepoDao {
   @Query(<query>)
   fun someQueryMethod(): LiveData<List<data_model_object>>
   fun someQueryMethod(): DataSource.Factory<Int, data_model_object>...}

Step 3. Build and Config the paged list:

To build and configure a LiveData<PagedList>, use a LivePagedListBuilder.

//get the dataSourceFactory by making a call to dao function
val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()

Besides the DataSource.Factory, you need to provide a PagedList configuration, which can include the following options:

  • The size of a page loaded by a PagedList
  • How far ahead to load
  • How many items to load when the first load occurs during the creation of pagedList by DataSource.
  • Whether null items can be added to the PagedList, to display placeholders for data that hasn’t been loaded yet. As soon as the data becomes available the ViewHolder gets populated with that and the view is updated with a nice cross-fade animation. If you have it disabled then your scroll bar jumps whenever data loads and that gives rise to bad user experience.
    But there are some problems with placeholders:
    a) All the items should be of the same size otherwise the animation looks weird.
    b) The adapter must handle null items.
    c) DataSource must count items (room does that implicitly).

You can have a separate config object to add configurations.

val config = PagedList.Config.Builder()
.setPageSize(30) //mandatory to provide
.setInitialLoadSizeHint(50) //default: page size*3
.setPrefetchDistance(10) //default: page size
.setEnablePlaceholder(true/false) //default: true
.build()val data = LivePagedListBuilder(dataSourceFactory, config).build()

Note: The DataSource page size should be several screens’ worth of items. If the page is too small, your list might flicker as pages content doesn’t cover the full screen. Larger page sizes are good for loading efficiency, but can increase latency when the list is updated.

Step 4. Make the RecyclerView Adapter work with a PagedList:

To bind a PagedList to a RecycleView, use a PagedListAdapter. The PagedListAdapter gets notified whenever the PagedList content is loaded and then signals the RecyclerView to update.

class RecyclerViewAdapter : PagedListAdapter<data_model_object, RecyclerView.ViewHolder>(DATA_COMPARATOR)

Also notice that now the data item is nullable and we have to update the onBindViewHolder to make it nullable. You can work with that data by applying checks if you want.

val item:data_model_object? = getItem(position)// optional        
if (item != null) {            
(holder as itemViewHolder).bind(item)        }

Step 5. Trigger network updates:

If your entire data is on a local database then we are done and you do not need to perform this step. But if your local DB is backed up by a server then we should find to way to ask for more data from the server only when there is no more data in the local DB. By providing all the data to the UI from the DB itself we follow the single source of truth principle.

For this, we use BoundaryCallback which gives two methods to perform some task when there are zero items in the list or the last available has been loaded so we need more data.

For this create a class extending the BoundaryCallback class and provide it the service and the local DB instances. Then override the methods to update the DB with your server data.

class sampleBoundaryCallback(
       private val service: ApiService,
       private val db: localDb,
       private val query:String
) : PagedList.BoundaryCallback<data_model_object>() {
   override fun onZeroItemsLoaded() {
        loadDataFromServerAndStoreInDB()}

   override fun onItemAtEndLoaded(itemAtEnd: data_model_object) {
        loadDataFromServerAndStoreInDB()    }
}

Then add the callback to LivePagedListBuilder:

val boundaryCallback = SampleBoundaryCallback(service, db, query)

   // Get the paged list
   val data = LivePagedListBuilder(dataSourceFactory, config)
            .setBoundaryCallback(boundaryCallback)
            .build()

Wrap up:

Now that we added all the components, let’s take a step back and see how everything works together.

The DataSource.Factory (implemented by Room) creates the DataSource. Then, LivePagedListBuilder builds the LiveData<PagedList>, using the passed-in DataSource.Factory, BoundaryCallback, and PagedList configuration. This LivePagedListBuilder object is responsible for creating PagedList objects. When a PagedList is created, two things happen at the same time:

  • The LiveData emits the new PagedList to the ViewModel, which in turn passes it to the UI. The UI observes the changed PagedList and uses its PagedListAdapter to update the RecyclerView that presents the PagedList data. (The PagedList is represented in the following animation by an empty square).
  • The PagedList tries to get the first chunk of data from the DataSource. When the DataSource is empty, for example when the app is started for the first time and the database is empty, it calls BoundaryCallback.onZeroItemsLoaded(). In this method, the BoundaryCallback requests more data from the network and inserts the response data in the database.
image

After the data is inserted in the DataSource, a new PagedList object is created (represented in the following animation by a filled-in square). This new data object is then passed to the ViewModel and UI using LiveData and displayed with the help of the PagedListAdapter.

image

When the user scrolls, the PagedList requests that the DataSource load more data, querying the database for the next chunk of data. When the PagedList paged all the available data from the DataSource, BoundaryCallback.onItemAtEndLoaded() is called. The BoundaryCallback requests data from the network and inserts the response data in the database. The UI then gets re-populated based on the newly-loaded data.

image

You can find a sample project implementing Paging here. I will also make a video tutorial for paging on youtube and will add the link here.

Feel free to comment if there are doubts or if you feel anything needs to be corrected😀.

Resources:

IO18’ Paging library launch

Android Codelab

Android Developers documentation

.

.

.

.

.

https://www.zoftino.com/pagination-in-android-using-paging-library

Setup

To include paging library in your project, you need to add below entry to build.gradle file.

def paging_version = "1.0.0"
implementation "android.arch.paging:runtime:$paging_version"

Since RecyclerView, Room, ViewModle and LiveData are used in the examples, we need to add following entries as well.

    implementation 'com.android.support:recyclerview-v7:28.0.0-rc01'

    def lifecycle_version = "1.1.1"
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"

    def room_version = "1.1.1"
    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version"

How It Works

A collection  (PagedList)  which acts as mediator between UI and Database uses data source to asynchronously fetch page data and populates itself with the loaded data.  (PagedList 는 UI와 데이터베이스 사이에 위치하며 data source를 이용하여 데이터를 가져온다. 새로 가져온 데이터를 가지고 PagedList 가 생성된다. )  The collection is passed to UI components thru listeners for displaying it in UI.

The collection or list is provided with configuration which contains initial load size and page size.  (PagedList 는 page관련 config정보를 가지며 page관련 정보도 관리한다)  The list manages page parameters and passes them to data source so that it can use it to load data according to configuration.

In response to UI events, UI component sends signal to the collection for next page.  (UI에서 더이상 보여줄 데이터가 없어진 경우 이를 PagedList 알리고 추가로 데이터를 가져오게 한다 ) The collection fetches data for next page and observer or listener passes the loaded data to UI component for displaying.

Paging Library Components

The collection mentioned above is    PagedList    class in paging library and it is created using    LivePagedListBuilder   (이를 통해 PagedList를 만든다 ). LivePagedListBuilder is supplied with data source factory and page list configuration objects.   (LivePagedListBuilder 만들때 전달해야 하는 parameters로는 data source factory, pagedlist의 config내용이 있다)

PagedList object is passed to recycler view adapter which can handle PagedList object. PagedList object is passed to recycler view by creating LiveData of PagedList.    (PagedList는 livedata형태로 adapater에 전달된다)   The observer of LiveData passes the PagedList object to the recycler view adapter which takes care of displaying data in RecyclerView, sending next page signal to PagedList and listening to PagedList for data updates.   (adapter는 다음 page를 위한 신호를 PagedList에 보내고 data update를 기대한다)

The important behavior of PagedList is that it can’t reload records if data is updated in the database. To show updates of already loaded data, new PagedList object has to be created.   (데이터가 업데이트된 경우 PagedList는 데이터를 reload하는것이 아니고 새로운 PagedList를 생성한다 )

DataSource.Factory    which is passed to    LivePagedListBuilder   is a factory for data source.    (DataSource.Factory는  data source를 생성하는 역할을 하며  LivePagedListBuilder에 전달된다)   You need to create DataSource.Factory and override create() method which returns DataSource object.   (DataSource.Factory의 create()를 override하고 이것의 리턴값이 DataSource object가 되게 해야한다)

DataSource    is used for loading data by PagedList. You can create DataSource by extending one of the three data source classes such as    PageKeyedDataSource, ItemKeyedDataSource, or PositionalDataSource.    (DataSource는 PagedList에 의해 데이터를 load하는 작업을 수행하며 PageKeyedDataSource, ItemKeyedDataSource, PositionalDataSource 중 하나를 extend한다)   You need to implement loadInitial and loadAfter methods. Using parameters in loadInitial and loadAfter methods, you can prepare query and load data.   (DataSource는 loadInitial, loadAfter 두 메소드를 implement한다 )

PageKeyedDataSource and ItemKeyedDataSource can be used to load data based on key and size. They differ the way last loaded key is captured and passed as parameter to next call.

PositionalDataSource interacts with source of data which can provide data at any position and loads required number of items.

PagedListAdapter    is a recycler view adapter for displaying data from PagedList. Initial data is passed to PagedListAdapter by calling submitList on the adapter and passing the list.

PagedListAdapter calls loadAround on PagedList object to make PagedList load data for next pages as user scrolls the items in recycler view. PagedListAdapter listens for data changes and displays the changes in recycler view.   (PagedListAdapter 는 다음페이지를 위해 PagedList object의 loadAround를 호출한다)

.

.

.

.

.

https://youtu.be/c_v0y2k-bww

10분까지 내용 개념 성명 좋음

image
image
image
image
image
image
image
image
image

.

.

.

.

.

실제 예제 코드

orginal source : https://www.zoftino.com/pagination-in-android-using-paging-library

DataSource Factory

You need to extend DataSource Factory class provided by paging library and implement create() method which returns DataSource.

import android.arch.paging.DataSource;
import android.content.Context;

/**
 * data source factory passed to PageList which calls create method to get
 * data source object
 */
public class CouponsDataSourceFactory extends DataSource.Factory<Integer, Coupon>  {
    private Context ctx;
    private CouponsDataSource couponsDataSource;
    
    public CouponsDataSourceFactory(Context ctx){
            this.ctx = ctx;
    }
    @Override
    public DataSource<Integer, Coupon> create() {
        if(couponsDataSource == null){
            couponsDataSource = new CouponsDataSource(ctx);
        }
        return couponsDataSource;
    }
}

DataSource

DataSource returns data to PageList. For our example, data source is created by extending PageKeyedDataSource class as we are going to use record id as key. You can use ItemKeyedDataSource as well. We need to override loadInitial() and loadAfter() methods of PageKeyedDataSource. In the data source, we use Room DAO to load data from the local database. In both the callack methods loadInitial() and loadAfter(), you can obtain the key from load parameters passed to them and pass the key to DAO to load data corresponding to the page being requested.

Our example initially loads records from 0 to 20 and then for next pages, it loads 25 records starting from the last loaded data.

import android.arch.paging.PageKeyedDataSource;
import android.content.Context;
import android.support.annotation.NonNull;

import java.util.List;

//data source for PagedList, it is used for loading data for each page
public class CouponsDataSource extends PageKeyedDataSource<Integer, Coupon> {
    private CouponLocalDAO couponDAO;

    public CouponsDataSource(Context ctx){
        couponDAO = LocalRepository.getCouponDB(ctx).couponDAO();
    }
    //is called too load initial data
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params,
                            @NonNull LoadInitialCallback<Integer, Coupon> callback) {

        List<Coupon> cpns = couponDAO.getCouponsBySize(0, params.requestedLoadSize);

        //this is required to handle first request after db is created or app is installed
        int noOfTryies = 0;
        while(cpns.size() == 0){
            cpns = couponDAO.getCouponsBySize(0, params.requestedLoadSize);
            noOfTryies++;
            if(noOfTryies == 6){
                break;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {  }
        }

        callback.onResult(cpns,null,
                cpns.size()+1);

    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params,
                           @NonNull LoadCallback<Integer, Coupon> callback) {

    }

    //is called to load pages of data using key passed in params
    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params,
                          @NonNull LoadCallback<Integer, Coupon> callback) {
        List<Coupon> cpns = couponDAO.getCouponsBySize( params.key, params.requestedLoadSize);
        int nextKey = params.key+cpns.size();
        callback.onResult(cpns, nextKey);
    }
}

ViewModel

It contains view model factory which is need so that context can be passed to ViewModel as context is needed for initializing Room database.

In ViewModel constructor, data source factory defined above is instantiated and page list config object is created. Factory and config objects are passed to LivePagedListBuilder to build LiveData of PagedList object.

mport android.app.Application;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;

public class CouponViewModel extends ViewModel {
    //PagedList controls data loading using data source
    public LiveData<PagedList<Coupon>> couponList;

    public CouponViewModel(Application application){

        //instantiate CouponsDataSourceFactory
        CouponsDataSourceFactory factory = new CouponsDataSourceFactory(application);

        //create PagedList Config
        PagedList.Config config = (new PagedList.Config.Builder()).setEnablePlaceholders(true)
                                .setInitialLoadSizeHint(20)
                                .setPageSize(25).build();

        //create LiveData object using LivePagedListBuilder which takes
        //data source factory and page config as params
        couponList = new LivePagedListBuilder<>(factory, config).build();
    }

    //factory for creating view model,
    // required because we need to pass Application to view model object
    public static class CouponViewModelFactory extends ViewModelProvider.NewInstanceFactory {
        private Application mApplication;
        public CouponViewModelFactory(Application application) {
            mApplication = application;
        }
        @Override
        public <T extends ViewModel> T create(Class<T> viewModel) {
            return (T) new CouponViewModel(mApplication);
        }
    }
}

Recycler View Adapter PagedListAdapter

To display data in recycler view and interact with PagedList object, we need to create adapter class which extends PagedListAdapter.

You need to implement DiffUtil.ItemCallback and implement areItemsTheSame and areContentsTheSame methods. DiffUtil.ItemCallback object, which is used to compare data objects, is passed to super construction of your adapter class.

Initial data is passed to PagedListAdapter by calling submitList method on it. Internally it handles recycler view scroll events and sends the next page load signals to PagedList by calling loadAround on paged list object.

import android.arch.paging.PagedListAdapter;
import android.support.annotation.NonNull;
import android.support.v7.util.DiffUtil;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/** this adapter displays coupon items in recycler view
 *  it extends PagedListAdapter which gets data from PagedList
 *  and displays in recycler view as data is available in PagedList
 */
public class CouponAdapter extends PagedListAdapter<Coupon, CouponViewHolder> {

    protected CouponAdapter() {
        super(DIFF_CALLBACK);
    }

    //DiffUtil is used to find out whether two object in the list are same or not
    public static DiffUtil.ItemCallback<Coupon> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Coupon>() {
        @Override
        public boolean areItemsTheSame(@NonNull Coupon oldCoupon,
                                       @NonNull Coupon newCoupon) {
            return oldCoupon.get_id() == newCoupon.get_id();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Coupon oldCoupon,
                                          @NonNull Coupon newCoupon) {
            return oldCoupon.equals(newCoupon);
        }
    };

    @Override
    public CouponViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater li = LayoutInflater.from(parent.getContext());
        View view = li.inflate(R.layout.coupon_item, parent, false);
        return new CouponViewHolder(view);
    }

    @Override
    public void onBindViewHolder(CouponViewHolder holder, int position) {
        Coupon coupon = getItem(position);
        if(coupon != null) {
            holder.bindTO(coupon);
        }
    }
}

Activity

In the activity, create view model and adapter objects, set recycler view adapter and listen to live data object which exists in view model. In the handler, pass the paged list to adapter by calling the submitList method.

import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.coupons_rv);

        CouponViewModel viewModel = ViewModelProviders.of(this,
                new CouponViewModel.CouponViewModelFactory(this.getApplication()))
                .get(CouponViewModel.class);

        CouponAdapter adapter = new CouponAdapter();
        recyclerView.setAdapter(adapter);

        //listen to data changes and pass it to adapter for displaying in recycler view
        viewModel.couponList.observe(this, pagedList -> {
            adapter.submitList(pagedList);
        });

        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        DividerItemDecoration dividerItemDecoration =
                new DividerItemDecoration(recyclerView.getContext(),
                        LinearLayoutManager.VERTICAL);
        recyclerView.addItemDecoration(dividerItemDecoration);
    }
}

Other Classes

Following are other classes created for this examples.

RecyclerView ViewHolder

public class CouponViewHolder extends RecyclerView.ViewHolder {
    public TextView storeNameTv;
    public TextView couponTv;

    public CouponViewHolder(View view) {
        super(view);
        storeNameTv = view.findViewById(R.id.coupon_store);
        couponTv = view.findViewById(R.id.coupon_tv);
    }

    public void bindTO(Coupon coupon){
        storeNameTv.setText(coupon.getStore());
        couponTv.setText(coupon.getOffer());
    }
}

Entity

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class Coupon {
    @PrimaryKey(autoGenerate = true)
    private int _id;
    private String store;
    private String offer;

    public Coupon(){}
    public Coupon(String store, String coupons){
        this.store = store;
        this.offer = coupons;
    }

    public String getStore() {
        return store;
    }

    public void setStore(String store) {
        this.store = store;
    }

    public int get_id() {
        return _id;
    }

    public void set_id(int _id) {
        this._id = _id;
    }

    public String getOffer() {
        return offer;
    }

    public void setOffer(String offer) {
        this.offer = offer;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        Coupon coupon = (Coupon)obj;
        return coupon.get_id() == this.get_id() &&
                coupon.getStore() == coupon.getStore() &&
                coupon.getOffer() == coupon.getOffer();
    }
}

Room DAO

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;

import java.util.List;

@Dao
public interface CouponLocalDAO {
    //to fetch data required to display in each page
    @Query("SELECT * FROM Coupon WHERE  _id >= :id LIMIT :size")
    public List<Coupon> getCouponsBySize(int id, int size);

    //this is used to populate db
    @Insert
    public void insertCoupons(List<Coupon> coupons);
}


Room Database

import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

@Database(entities = {Coupon.class}, version = 1)
public abstract class CouponsDB extends RoomDatabase {
    public abstract CouponLocalDAO couponDAO();
}

Repository

import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;

public class LocalRepository {
    private static CouponsDB couponDB;
    private static final Object LOCK = new Object();
    private static Context ctx;

    public synchronized static CouponsDB getCouponDB(Context context) {
        if (couponDB == null) {
            ctx = context;
            synchronized (LOCK) {
                if (couponDB == null) {
                    couponDB = Room.databaseBuilder(context,
                            CouponsDB.class, "Coupons Database")
                            .fallbackToDestructiveMigration()
                            .addCallback(dbCallback).build();

                }
            }
        }
        return couponDB;
    }

    private static RoomDatabase.Callback dbCallback = new RoomDatabase.Callback() {
        public void onCreate(SupportSQLiteDatabase db) {

            Executors.newSingleThreadScheduledExecutor().execute(new Runnable() {
                @Override
                public void run() {
                    addCoupons(ctx);
                }
            });
        }
    };

    private static void addCoupons(Context ctx){
        List<Coupon> couponList = new ArrayList<Coupon>();

        for(String s : initCoupons){
            String[] ss = s.split("\|");
            couponList.add(new Coupon(ss[0], ss[1]));
        }
        getCouponDB(ctx).couponDAO().insertCoupons(couponList);
    }
    private static String[] initCoupons = {"amazon|falt 20% off on fashion",
            "amazon|upto 30% off on electronics",
            "ebay|falt 20% off on fashion", "ebay|upto 40% off on electronics",
            "nordstorm|falt 30% off on fashion", "bestbuy|upto 80% off on electronics",
            "sears|falt 60% off on fashion", "ee|upto 40% off on electronics",
            "macys|falt 30% off on fashion", "alibaba|upto 90% off on electronics",
            "nordstorm|falt 90% off on fashion", "ebay|upto 40% off on electronics",
            "nordstorm|falt 30% off on fashion", "ebay|upto 70% off on electronics",
            "jcpenny|falt 50% off on fashion", "ebay|upto 50% off on electronics",
            "khols|falt 70% off on fashion", "ebay|upto 40% off on electronics",
            "target|falt 30% off on fashion", "ebay|upto 20% off on electronics",
            "costco|falt 80% off on fashion", "ebay|upto 40% off on electronics",
            "walmart|falt 10% off on fashion", "ebay|upto 10% off on electronics",
            "nordstorm|falt 30% off on fashion", "ebay|upto 70% off on electronics",
            "ebay|falt 40% off on fashion", "ebay|upto 40% off on electronics",
            "nordstorm|falt 70% off on fashion", "ebay|upto 80% off on electronics",
            "nordstorm|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "nordstorm|falt 60% off on fashion", "ebay|upto 50% off on electronics",
            "ebay|falt 30% off on fashion", "ebay|upto 70% off on electronics",
            "ebay|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "uuuu|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "tttt|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "ssss|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "eee|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "www|falt 30% off on fashion", "ebay|upto 40% off on electronics",
            "rrrr|falt 30% off on fashion", "tyyyy|upto 40% off on electronics",
            "vvvv|falt 30% off on fashion", "wwwwe|upto 40% off on electronics",
            "bbbb|falt 30% off on fashion", "ssssssssssssa|upto 40% off on electronics",
            "mmmm|falt 30% off on fashion", "rrtttt|upto 40% off on electronics",

    };
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
    android:id="@+id/coupons_rv"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>

Item Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="8dp">

    <TextView
        android:id="@+id/coupon_store"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/coupon_tv"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <TextView
        android:id="@+id/coupon_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="8dp"
        android:textColor="@color/colorAccent"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        app:layout_constraintLeft_toRightOf="@+id/coupon_store"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>

ItemKeyedDataSource Example

Following class is an example of ItemKeyedDataSource Which can be used instead of PageKeyedDataSource used in the above example.

public class CouponsItemKeyDataSource extends ItemKeyedDataSource<Integer, Coupon> {
    private CouponLocalDAO couponDAO;

    public CouponsItemKeyDataSource(Context ctx){
        couponDAO = LocalRepository.getCouponDB(ctx).couponDAO();
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Coupon> callback) {
        List<Coupon> cpns = couponDAO.getCouponsBySize(0, params.requestedLoadSize);

        callback.onResult(cpns, 0, cpns.size());
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Coupon> callback) {
        List<Coupon> cpns = couponDAO.getCouponsBySize( params.key, params.requestedLoadSize);
        callback.onResult(cpns);
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Coupon> callback) {

    }

    @NonNull
    @Override
    public Integer getKey(@NonNull Coupon item) {
        return item.get_id();
    }
}

https://youtu.be/YogGlRYCfp0?t=285

처음 시작은 위의 코드를 보고 알아 보게 되었다.

.

.

interface delegation에 대하여

https://youtu.be/zfiohSIZtbo

https://youtu.be/JolUNygXu3s

.

.

property delegation에 대하여

https://kotlinlang.org/docs/reference/delegated-properties.html

.

.

lazy 에 대하여

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-lazy/

https://www.youtube.com/watch?v=orUgnUuYW44

.

.

lazyDeferred의 구현 예시 

https://stackoverflow.com/questions/57447027/how-to-refresh-viewmodel-data-using-kotlin-lazy-loading-coroutine

fun <T> lazyDeferred(block: suspend CoroutineScope.() -> T): Lazy<Deferred<T>> {
    return lazy {
        GlobalScope.async {
            block.invoke(this)
        }
    }
}

original source : https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546

image

Constructors vs Initializers

One aspect of class creation in Kotlin that might surprise people coming from other languages is that, when writing a class, there a few places that you can define code that will be executed when you create an instance of the class:

Property initializers

When you declare a property, you can immediately assign it a value like this:

val count: Int = 0

You’ll frequently use constant values as initializers, but you can also call a function or use a property delegate in order to run arbitrary code.

Initializer blocks

If you want more complex code, you can also use an initializer block, defined with the init keyword:

init {
   /* do some setup here */
}

You can define those blocks anywhere at the top level of a class declaration, and they will be executed as part of class construction. You can even define more than one initializer block if you want, and they will all be executed.

Constructors

Kotlin also has constructors, which can be defined in the class header or in the body of the class definition. You can define multiple secondary constructors, but only one will be called when you create a class instance unless the constructor explicitly calls another one. Constructors can also have default argument values which are evaluated each time the constructor is called. Like property initializers, these can be function calls or other expressions that will run arbitrary code.

Execution order

All of those features are great, but it can be easy to overlook how they all interact, especially when inheritance is involved. So what order will code run if you define a class with all of them?

First, default constructor arguments are evaluated, starting with argument to the constructor you call directly, followed by arguments to any delegated constructors. Next, initializers (property initializers and init blocks) are executed in the order that they are defined in the class, top-to-bottom. Finally, constructors are executed, starting with the primary constructor and moving outward through delegated constructors until the constructor that you called is executed. The constructor order is probably the most surprising, since no matter where in the class the constructor is defined, it is always executed after all initializers have run.

That’s a lot of rules, so let’s create an small program to help us visualize all of this:

open class Parent {
    private val a = println("Parent.a")

    constructor(arg: Unit=println("Parent primary constructor default argument")) {
        println("Parent primary constructor")
    }

    init {
        println("Parent.init")
    }

    private val b = println("Parent.b")
}

class Child : Parent {
    val a = println("Child.a")

    init {
        println("Child.init 1")
    }

    constructor(arg: Unit=println("Child primary constructor default argument")) : super() {
        println("Child primary constructor")
    }

    val b = println("Child.b")

    constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument")): this() {
        println("Child secondary constructor")
    }

    init {
        println("Child.init 2")
    }
}

If we construct an instance of Child by calling its secondary constructor with Child(1), what will be printed to the console?

Here’s the output:

Child secondary constructor default argument
Child primary constructor default argument
Parent primary constructor default argument
Parent.a
Parent.init
Parent.b
Parent primary constructor
Child.a
Child.init 1
Child.b
Child.init 2
Child primary constructor
Child secondary constructor

As you can see, initializers are run top to bottom at the beginning of a class’ primary constructor. If you call a secondary constructor, the constructor that gets delegated to will be run before the secondary constructor. And most importantly, superclasses will be fully constructed before any subclass constructors will be run.

original source : https://youtu.be/M15PEeHXM64 

33분량

select list형태의 경우 아래와 같이 array를 만들어 사용한다.

image

.

.

preference activity 나 preference fragment에 사용할 화면 구성하기 

image
image
image

.

.

preference activity 나 preference fragment의 형태 이경우에는 preference activity 안에 preference fragment를 포함 하는 형태로 만들었는데 반드시 포함해야 하는 것은 아니다.

image

.

.

저장된 Preferences를 통해 저장된 SharedPreferences값을 불러오는 방법과 일반 SharedPreferences사용방법

image

.

.

SharedPreferences와 Preferences 차이

https://stackoverflow.com/a/23223316/3151712

.

.

.

.

https://youtu.be/M15PEeHXM64

30분 분량

.

.

.

.

https://youtu.be/SfeRakSWWbk

16분분량

.

.

.

.

SharedPreferences Preferences setOnPreferenceChangeListener onPreferenceChange alert alertdialog

https://stackoverflow.com/a/21857176/3151712

https://stackoverflow.com/a/11531922/3151712

dialogpreference

https://developer.android.com/reference/android/preference/DialogPreference?hl=en

extend 

https://stackoverflow.com/a/40058062/3151712

.

.

.

.

Preference EditTextPreference 속성 attributes 바꾸기

https://stackoverflow.com/a/55461028/3151712