Giter Site home page Giter Site logo

books's Introduction

README.md

Architecture

Untitled


Tech Stack

  • Kotlin
  • Coroutines
  • Hilt
  • Retrofit
  • Room
  • Splash screens
  • ViewBinding
  • ViewModel
  • Lifecycle
  • Glide
  • Timber

Feature

검색

SearchBookFragment.kt

// 검색 버튼을 눌렀을 때
searchQueryEditText.setOnEditorActionListener { editText, actionId, _ ->
    if (actionId == EditorInfo.IME_ACTION_SEARCH) {
        val query = editText.text.toString()

        if (query.isNotBlank()) {
            viewModel.search(query)
            ..
        }
    }
    ..
}

SearchBookViewModel.kt

sealed class LoadParams {
    data class Refresh(val query: String): LoadParams()
    data class Append(val query: String, val index: Int): LoadParams()
}

fun search(query: String) {
    viewModelScope.launch {
        refresh.emit(LoadParams.Refresh(query))
        ..
    }
}

val _searchResults: StateFlow<BookSearchResults?> = refresh.filterNotNull()
    .combine(append) { refresh, append ->
        // 검색
        if (refresh.query != append?.query) {
            _loading.emit(Unit)
            SearchBookParams(refresh.query)
        } else {
            SearchBookParams(append.query, append.index)
        }
    }
    .mapLatest { searchBookUseCase(it) }
    .map { result ->
        // 에러 전파
        if (result is Result.Error) {
            _error.emit(result.exception)
        }
        result.successOr(null)
    }
    .filterNotNull()
    .runningReduce { accumulator, refreshOrAppend ->
        if (accumulator.query == refreshOrAppend.query) {
            // 페이지네이션
            if (refreshOrAppend.totalCount != 0) {
                accumulator.copy(data = (accumulator.data + refreshOrAppend.data).distinctBy { it.id })
            } else {
                accumulator
            }
        } else {
            // PATH 새로운 검색어로 검색한 결과
            refreshOrAppend
        }
    }
    .flowOn(Dispatchers.Default)
    .stateIn(viewModelScope, SharingStarted.Eagerly, null)

val searchResults: Flow<BookSearchResults> = _searchResults.filterNotNull()

SearchBookResultsFragment.kt

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch {
            viewModel.searchResults.collect { results ->
                binding.apply {
                    searchResultsTotalCountTextView.text = requireContext().getString(
                        R.string.search_results_total_count,
                        results.totalCount
                    )

                    adapter.submitList(results.data) {
                        loadingView.root.isVisible = false
                    }

                    noSearchResultView.root.isVisible = results.data.isEmpty()
                }
            }
	    }
    }
}

페이지네이션

searchResultsRecyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        val lastCompletelyVisibleItemPosition = (recyclerView.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition()

        // 마지막 아이템 -10번째 아이템에서 다음 페이지 로드
        if (lastCompletelyVisibleItemPosition != null && lastCompletelyVisibleItemPosition >= adapter.itemCount - 10) {
            viewModel.load(adapter.itemCount)
        }
    }
})

SearchBookViewModel.kt

fun load(index: Int) {
    viewModelScope.launch {
        val query= requireNotNull(refresh.value?.query)

        append.emit(LoadParams.Append(query, index))
    }
}

val _searchResults: StateFlow<BookSearchResults?> = refresh.filterNotNull()
    .combine(append) { refresh, append ->
        if (refresh.query != append?.query) {
            _loading.emit(Unit)
            SearchBookParams(refresh.query)
        } else {
            // 다음 페이지 로드
            SearchBookParams(append.query, append.index)
        }
    }
    .mapLatest { searchBookUseCase(it) }
    .map { result ->
        // 에러 전파
        if (result is Result.Error) {
            _error.emit(result.exception)
        }
        result.successOr(null)
    }
    .filterNotNull()
    .runningReduce { accumulator, refreshOrAppend ->
        if (accumulator.query == refreshOrAppend.query) {
            if (refreshOrAppend.totalCount != 0) {
                // PATH #1 이전 검색 결과와 페이징 데이터를 중복없이 합치는 과정
                accumulator.copy(data = (accumulator.data + refreshOrAppend.data).distinctBy { it.id })
            } else {
                // PATH #2 더 이상 불러올 데이터가 없을 때
                accumulator
            }
        } else {
            refreshOrAppend
        }
    }
    .flowOn(Dispatchers.Default)
    .stateIn(viewModelScope, SharingStarted.Eagerly, null)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.