Replace Timber with Logger

This commit is contained in:
Kirill Kamakin
2022-08-05 20:16:29 +02:00
parent ba5f7322ab
commit 107bb64256
49 changed files with 458 additions and 260 deletions

View File

@@ -4,25 +4,42 @@ import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
class RecipeViewHolder(
class RecipeViewHolder private constructor(
private val logger: Logger,
private val binding: ViewHolderRecipeBinding,
private val recipeImageLoader: RecipeImageLoader,
private val clickListener: (RecipeSummaryEntity) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
@Singleton
class Factory @Inject constructor(
private val logger: Logger,
) {
fun build(
recipeImageLoader: RecipeImageLoader,
binding: ViewHolderRecipeBinding,
clickListener: (RecipeSummaryEntity) -> Unit,
) = RecipeViewHolder(logger, binding, recipeImageLoader, clickListener)
}
private val loadingPlaceholder by lazy {
binding.root.resources.getString(R.string.view_holder_recipe_text_placeholder)
}
fun bind(item: RecipeSummaryEntity?) {
Timber.v("bind() called with: item = $item")
logger.v { "bind() called with: item = $item" }
binding.name.text = item?.name ?: loadingPlaceholder
recipeImageLoader.loadRecipeImage(binding.image, item)
item?.let { entity ->
binding.root.setOnClickListener {
Timber.d("bind: item clicked $entity")
logger.d { "bind: item clicked $entity" }
clickListener(entity)
}
}

View File

@@ -13,27 +13,34 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
import gq.kirmanak.mealient.extensions.collectWhenViewResumed
import gq.kirmanak.mealient.extensions.refreshRequestFlow
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import gq.kirmanak.mealient.ui.recipes.images.RecipePreloaderFactory
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class RecipesFragment : Fragment(R.layout.fragment_recipes) {
private val binding by viewBinding(FragmentRecipesBinding::bind)
private val viewModel by viewModels<RecipeViewModel>()
private val activityViewModel by activityViewModels<MainActivityViewModel>()
@Inject
lateinit var logger: Logger
@Inject
lateinit var recipeImageLoader: RecipeImageLoader
@Inject
lateinit var recipePagingAdapterFactory: RecipesPagingAdapter.Factory
@Inject
lateinit var recipePreloaderFactory: RecipePreloaderFactory
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
activityViewModel.updateUiState {
it.copy(loginButtonVisible = true, titleVisible = false, navigationVisible = true)
}
@@ -41,7 +48,7 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
}
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
Timber.v("navigateToRecipeInfo() called with: recipeSummaryEntity = $recipeSummaryEntity")
logger.v { "navigateToRecipeInfo() called with: recipeSummaryEntity = $recipeSummaryEntity" }
findNavController().navigate(
RecipesFragmentDirections.actionRecipesFragmentToRecipeInfoFragment(
recipeSlug = recipeSummaryEntity.slug,
@@ -51,29 +58,32 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
}
private fun setupRecipeAdapter() {
Timber.v("setupRecipeAdapter() called")
val recipesAdapter = RecipesPagingAdapter(recipeImageLoader, ::navigateToRecipeInfo)
logger.v { "setupRecipeAdapter() called" }
val recipesAdapter = recipePagingAdapterFactory.build(
recipeImageLoader = recipeImageLoader,
clickListener = ::navigateToRecipeInfo
)
with(binding.recipes) {
adapter = recipesAdapter
addOnScrollListener(recipePreloaderFactory.create(recipesAdapter))
}
collectWhenViewResumed(viewModel.pagingData) {
Timber.v("setupRecipeAdapter: received data update")
logger.v { "setupRecipeAdapter: received data update" }
recipesAdapter.submitData(lifecycle, it)
}
collectWhenViewResumed(recipesAdapter.onPagesUpdatedFlow) {
Timber.v("setupRecipeAdapter: pages updated")
logger.v { "setupRecipeAdapter: pages updated" }
binding.refresher.isRefreshing = false
}
collectWhenViewResumed(binding.refresher.refreshRequestFlow()) {
Timber.v("setupRecipeAdapter: received refresh request")
collectWhenViewResumed(binding.refresher.refreshRequestFlow(logger)) {
logger.v { "setupRecipeAdapter: received refresh request" }
recipesAdapter.refresh()
}
}
override fun onDestroyView() {
super.onDestroyView()
Timber.v("onDestroyView() called")
logger.v { "onDestroyView() called" }
// Prevent RV leaking through mObservers list in adapter
binding.recipes.adapter = null
}

View File

@@ -6,24 +6,40 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
class RecipesPagingAdapter(
class RecipesPagingAdapter private constructor(
private val logger: Logger,
private val recipeImageLoader: RecipeImageLoader,
private val recipeViewHolderFactory: RecipeViewHolder.Factory,
private val clickListener: (RecipeSummaryEntity) -> Unit
) : PagingDataAdapter<RecipeSummaryEntity, RecipeViewHolder>(RecipeDiffCallback) {
@Singleton
class Factory @Inject constructor(
private val logger: Logger,
private val recipeViewHolderFactory: RecipeViewHolder.Factory,
) {
fun build(
recipeImageLoader: RecipeImageLoader,
clickListener: (RecipeSummaryEntity) -> Unit,
) = RecipesPagingAdapter(logger, recipeImageLoader, recipeViewHolderFactory, clickListener)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
logger.v { "onCreateViewHolder() called with: parent = $parent, viewType = $viewType" }
val inflater = LayoutInflater.from(parent.context)
val binding = ViewHolderRecipeBinding.inflate(inflater, parent, false)
return RecipeViewHolder(binding, recipeImageLoader, clickListener)
return recipeViewHolderFactory.build(recipeImageLoader, binding, clickListener)
}
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {

View File

@@ -6,17 +6,18 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import dagger.hilt.android.scopes.FragmentScoped
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import timber.log.Timber
import gq.kirmanak.mealient.logging.Logger
import javax.inject.Inject
@FragmentScoped
class RecipeImageLoaderImpl @Inject constructor(
private val fragment: Fragment,
private val requestOptions: RequestOptions,
private val logger: Logger,
) : RecipeImageLoader {
override fun loadRecipeImage(view: ImageView, recipe: RecipeSummaryEntity?) {
Timber.v("loadRecipeImage() called with: view = $view, recipe = $recipe")
logger.v { "loadRecipeImage() called with: view = $view, recipe = $recipe" }
Glide.with(fragment).load(recipe).apply(requestOptions).into(view)
}
}

View File

@@ -7,16 +7,32 @@ import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.stream.BaseGlideUrlLoader
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.io.InputStream
import javax.inject.Inject
import javax.inject.Singleton
class RecipeModelLoader(
class RecipeModelLoader private constructor(
private val recipeImageUrlProvider: RecipeImageUrlProvider,
private val logger: Logger,
concreteLoader: ModelLoader<GlideUrl, InputStream>,
cache: ModelCache<RecipeSummaryEntity, GlideUrl>,
) : BaseGlideUrlLoader<RecipeSummaryEntity>(concreteLoader, cache) {
@Singleton
class Factory @Inject constructor(
private val recipeImageUrlProvider: RecipeImageUrlProvider,
private val logger: Logger,
) {
fun build(
concreteLoader: ModelLoader<GlideUrl, InputStream>,
cache: ModelCache<RecipeSummaryEntity, GlideUrl>,
) = RecipeModelLoader(recipeImageUrlProvider, logger, concreteLoader, cache)
}
override fun handles(model: RecipeSummaryEntity): Boolean = true
override fun getUrl(
@@ -25,7 +41,7 @@ class RecipeModelLoader(
height: Int,
options: Options?
): String? {
Timber.v("getUrl() called with: model = $model, width = $width, height = $height, options = $options")
logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" }
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.slug) }
}
}

View File

@@ -1,24 +1,24 @@
package gq.kirmanak.mealient.ui.recipes.images
import com.bumptech.glide.load.model.*
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import timber.log.Timber
import gq.kirmanak.mealient.logging.Logger
import java.io.InputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RecipeModelLoaderFactory @Inject constructor(
private val recipeImageUrlProvider: RecipeImageUrlProvider,
private val recipeModelLoaderFactory: RecipeModelLoader.Factory,
private val logger: Logger,
) : ModelLoaderFactory<RecipeSummaryEntity, InputStream> {
private val cache = ModelCache<RecipeSummaryEntity, GlideUrl>()
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<RecipeSummaryEntity, InputStream> {
Timber.v("build() called with: multiFactory = $multiFactory")
logger.v { "build() called with: multiFactory = $multiFactory" }
val concreteLoader = multiFactory.build(GlideUrl::class.java, InputStream::class.java)
return RecipeModelLoader(recipeImageUrlProvider, concreteLoader, cache)
return recipeModelLoaderFactory.build(concreteLoader, cache)
}
override fun teardown() {

View File

@@ -8,22 +8,23 @@ import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.request.RequestOptions
import dagger.hilt.android.scopes.FragmentScoped
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import timber.log.Timber
import gq.kirmanak.mealient.logging.Logger
import javax.inject.Inject
class RecipePreloadModelProvider(
private val adapter: PagingDataAdapter<RecipeSummaryEntity, *>,
private val fragment: Fragment,
private val requestOptions: RequestOptions,
private val logger: Logger,
) : ListPreloader.PreloadModelProvider<RecipeSummaryEntity> {
override fun getPreloadItems(position: Int): List<RecipeSummaryEntity> {
Timber.v("getPreloadItems() called with: position = $position")
logger.v { "getPreloadItems() called with: position = $position" }
return adapter.peek(position)?.let { listOf(it) } ?: emptyList()
}
override fun getPreloadRequestBuilder(item: RecipeSummaryEntity): RequestBuilder<*> {
Timber.v("getPreloadRequestBuilder() called with: item = $item")
logger.v { "getPreloadRequestBuilder() called with: item = $item" }
return Glide.with(fragment).load(item).apply(requestOptions)
}
@@ -31,10 +32,11 @@ class RecipePreloadModelProvider(
class Factory @Inject constructor(
private val fragment: Fragment,
private val requestOptions: RequestOptions,
private val logger: Logger,
) {
fun create(
adapter: PagingDataAdapter<RecipeSummaryEntity, *>,
) = RecipePreloadModelProvider(adapter, fragment, requestOptions)
) = RecipePreloadModelProvider(adapter, fragment, requestOptions, logger)
}
}

View File

@@ -14,8 +14,8 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
@@ -24,8 +24,17 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
private val binding by viewBinding(FragmentRecipeInfoBinding::bind)
private val arguments by navArgs<RecipeInfoFragmentArgs>()
private val viewModel by viewModels<RecipeInfoViewModel>()
private val ingredientsAdapter = RecipeIngredientsAdapter()
private val instructionsAdapter = RecipeInstructionsAdapter()
private val ingredientsAdapter by lazy { recipeIngredientsAdapterFactory.build() }
private val instructionsAdapter by lazy { recipeInstructionsAdapterFactory.build() }
@Inject
lateinit var recipeInstructionsAdapterFactory: RecipeInstructionsAdapter.Factory
@Inject
lateinit var recipeIngredientsAdapterFactory: RecipeIngredientsAdapter.Factory
@Inject
lateinit var logger: Logger
@Inject
lateinit var recipeImageLoader: RecipeImageLoader
@@ -35,13 +44,13 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Timber.v("onCreateView() called")
logger.v { "onCreateView() called" }
return FragmentRecipeInfoBinding.inflate(inflater, container, false).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called")
logger.v { "onViewCreated() called" }
with(binding) {
ingredientsList.adapter = ingredientsAdapter
@@ -55,7 +64,7 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
}
private fun onUiStateChange(uiState: RecipeInfoUiState) = with(binding) {
Timber.v("onUiStateChange() called")
logger.v { "onUiStateChange() called" }
ingredientsHolder.isVisible = uiState.areIngredientsVisible
instructionsGroup.isVisible = uiState.areInstructionsVisible
uiState.recipeInfo?.let {
@@ -72,7 +81,7 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
override fun onDestroyView() {
super.onDestroyView()
Timber.v("onDestroyView() called")
logger.v { "onDestroyView() called" }
// Prevent RV leaking through mObservers list in adapter
with(binding) {
ingredientsList.adapter = null

View File

@@ -7,32 +7,33 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import gq.kirmanak.mealient.data.recipes.RecipeRepo
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class RecipeInfoViewModel @Inject constructor(
private val recipeRepo: RecipeRepo,
private val logger: Logger,
) : ViewModel() {
private val _uiState = MutableLiveData(RecipeInfoUiState())
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
_uiState.value = RecipeInfoUiState()
viewModelScope.launch {
runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) }
.onSuccess {
Timber.d("loadRecipeInfo: received recipe info = $it")
logger.d { "loadRecipeInfo: received recipe info = $it" }
_uiState.value = RecipeInfoUiState(
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
areInstructionsVisible = it.recipeInstructions.isNotEmpty(),
recipeInfo = it,
)
}
.onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") }
.onFailure { logger.e(it) { "loadRecipeInfo: can't load recipe info" } }
}
}
}

View File

@@ -7,17 +7,40 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
class RecipeIngredientsAdapter :
ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
class RecipeIngredientsAdapter private constructor(
private val recipeIngredientViewHolderFactory: RecipeIngredientViewHolder.Factory,
private val logger: Logger,
) : ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
class RecipeIngredientViewHolder(
private val binding: ViewHolderIngredientBinding
@Singleton
class Factory @Inject constructor(
private val recipeIngredientViewHolderFactory: RecipeIngredientViewHolder.Factory,
private val logger: Logger,
) {
fun build() = RecipeIngredientsAdapter(recipeIngredientViewHolderFactory, logger)
}
class RecipeIngredientViewHolder private constructor(
private val binding: ViewHolderIngredientBinding,
private val logger: Logger,
) : RecyclerView.ViewHolder(binding.root) {
@Singleton
class Factory @Inject constructor(
private val logger: Logger,
) {
fun build(binding: ViewHolderIngredientBinding) =
RecipeIngredientViewHolder(binding, logger)
}
fun bind(item: RecipeIngredientEntity) {
Timber.v("bind() called with: item = $item")
logger.v { "bind() called with: item = $item" }
binding.checkBox.text = item.note
}
}
@@ -35,17 +58,17 @@ class RecipeIngredientsAdapter :
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeIngredientViewHolder {
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
logger.v { "onCreateViewHolder() called with: parent = $parent, viewType = $viewType" }
val inflater = LayoutInflater.from(parent.context)
return RecipeIngredientViewHolder(
return recipeIngredientViewHolderFactory.build(
ViewHolderIngredientBinding.inflate(inflater, parent, false)
)
}
override fun onBindViewHolder(holder: RecipeIngredientViewHolder, position: Int) {
Timber.v("onBindViewHolder() called with: holder = $holder, position = $position")
logger.v { "onBindViewHolder() called with: holder = $holder, position = $position" }
val item = getItem(position)
Timber.d("onBindViewHolder: item is $item")
logger.d { "onBindViewHolder: item is $item" }
holder.bind(item)
}
}

View File

@@ -8,11 +8,23 @@ import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
class RecipeInstructionsAdapter :
ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) {
class RecipeInstructionsAdapter private constructor(
private val logger: Logger,
private val recipeInstructionViewHolderFactory: RecipeInstructionViewHolder.Factory,
) : ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) {
@Singleton
class Factory @Inject constructor(
private val logger: Logger,
private val recipeInstructionViewHolderFactory: RecipeInstructionViewHolder.Factory,
) {
fun build() = RecipeInstructionsAdapter(logger, recipeInstructionViewHolderFactory)
}
private object RecipeInstructionDiffCallback :
DiffUtil.ItemCallback<RecipeInstructionEntity>() {
@@ -27,11 +39,19 @@ class RecipeInstructionsAdapter :
): Boolean = oldItem == newItem
}
class RecipeInstructionViewHolder(
private val binding: ViewHolderInstructionBinding
class RecipeInstructionViewHolder private constructor(
private val binding: ViewHolderInstructionBinding,
private val logger: Logger,
) : RecyclerView.ViewHolder(binding.root) {
@Singleton
class Factory @Inject constructor(private val logger: Logger) {
fun build(binding: ViewHolderInstructionBinding) =
RecipeInstructionViewHolder(binding, logger)
}
fun bind(item: RecipeInstructionEntity, position: Int) {
Timber.v("bind() called with: item = $item, position = $position")
logger.v { "bind() called with: item = $item, position = $position" }
binding.step.text = binding.root.resources.getString(
R.string.view_holder_recipe_instructions_step, position + 1
)
@@ -40,17 +60,17 @@ class RecipeInstructionsAdapter :
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeInstructionViewHolder {
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
logger.v { "onCreateViewHolder() called with: parent = $parent, viewType = $viewType" }
val inflater = LayoutInflater.from(parent.context)
return RecipeInstructionViewHolder(
ViewHolderInstructionBinding.inflate(inflater, parent, false)
return recipeInstructionViewHolderFactory.build(
ViewHolderInstructionBinding.inflate(inflater, parent, false),
)
}
override fun onBindViewHolder(holder: RecipeInstructionViewHolder, position: Int) {
Timber.v("onBindViewHolder() called with: holder = $holder, position = $position")
logger.v { "onBindViewHolder() called with: holder = $holder, position = $position" }
val item = getItem(position)
Timber.d("onBindViewHolder: item is $item")
logger.d { "onBindViewHolder: item is $item" }
holder.bind(item, position)
}
}