본문 바로가기

안드로이드

데이터바인딩에 리스너 붙여버리기 + 더러운코드를 어떻게 처리해야할까

아니 코드를 작성하다 너무 코드가 연쇄적으로 같은 코드가 나와서 너무 빡치고 왤케 내 코드는 항상 더러울까 싶어서

연주한테 한번 물어봤다

 

킹갓제너럴 연주가 아주 킹받아했지만 단숨에 해결해줬다

 

-> 해결책 리스너를 데이터바인딩으로 처리해서 좀 편하게 예쁘게 만들고
-> 코드를 간결하게 만들수도 있다!!

 

원래 코드

main Activity

package com.spark.android.ui.main

import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import com.spark.android.R
import com.spark.android.SparkApplication
import com.spark.android.databinding.ActivityMainBinding
import com.spark.android.ui.base.BaseActivity
import com.spark.android.ui.home.FeedFragment
import com.spark.android.ui.home.HomeMainFragment
import com.spark.android.ui.home.StorageFragment
import com.spark.android.ui.main.viewmodel.MainViewModel
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_FEED
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_HOME
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_STORAGE
import com.spark.android.util.FloatingAnimationUtil
import com.spark.android.util.initStatusBarColor
import com.spark.android.util.initStatusBarTextColorToWhite

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {

    private val mainViewModel by viewModels<MainViewModel>()
    private lateinit var feedFragment: FeedFragment
    private lateinit var homeMainFragment: HomeMainFragment
    private lateinit var storageFragment: StorageFragment
    private var fabState = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        initStatusBarStyle()
        initTransactionEvent()
        initBottomNavigationFeedButton()
        initBottomNavigationHomeButton()
        initBottomNavigationStorageButton()
        initBindingVariable()
        initFloatingButtonClickListener()
    }

    private fun initStatusBarStyle() {
        initStatusBarColor(R.color.spark_white)
        initStatusBarTextColorToWhite()
    }

    private fun initTransactionEvent() {
        feedFragment = FeedFragment()
        homeMainFragment = HomeMainFragment()
        storageFragment = StorageFragment()
        supportFragmentManager.beginTransaction().add(R.id.container_main, homeMainFragment)
            .commit()
    }


    private fun initBottomNavigationFeedButton() {
        binding.btnMainBottomNavFeed.setOnClickListener{
            supportFragmentManager.beginTransaction().replace(R.id.container_main, feedFragment).commit()
            binding.btnMainBottomNavFeed.setImageResource(R.drawable.ic_bottom_navigation_feed_selected)
            binding.tvMainBottomNavFeed.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_pinkred))
            binding.btnMainBottomNavHome.setImageResource(R.drawable.ic_bottom_navigation_home)
            binding.tvMainBottomNavHome.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.btnMainBottomNavStorage.setImageResource(R.drawable.ic_bottom_navigation_storage)
            binding.tvMainBottomNavStorage.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.fabHomeMain.visibility = View.GONE
            binding.fabHomeMakeRoom.visibility = View.GONE
            binding.fabHomeJoinCode.visibility = View.GONE
        }
    }

    private fun initBottomNavigationHomeButton() {
        binding.btnMainBottomNavHome.setOnClickListener{
            supportFragmentManager.beginTransaction().replace(R.id.container_main, homeMainFragment).commit()
            binding.btnMainBottomNavFeed.setImageResource(R.drawable.ic_bottom_navigation_feed)
            binding.tvMainBottomNavFeed.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.btnMainBottomNavHome.setImageResource(R.drawable.ic_bottom_navigation_home_selected)
            binding.tvMainBottomNavHome.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_pinkred))
            binding.btnMainBottomNavStorage.setImageResource(R.drawable.ic_bottom_navigation_storage)
            binding.tvMainBottomNavStorage.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.fabHomeMain.visibility = View.VISIBLE
            binding.fabHomeMakeRoom.visibility = View.VISIBLE
            binding.fabHomeJoinCode.visibility = View.VISIBLE
        }
    }

    private fun initBottomNavigationStorageButton() {
        binding.btnMainBottomNavStorage.setOnClickListener{
            supportFragmentManager.beginTransaction().replace(R.id.container_main, storageFragment).commit()
            binding.btnMainBottomNavFeed.setImageResource(R.drawable.ic_bottom_navigation_feed)
            binding.tvMainBottomNavFeed.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.btnMainBottomNavHome.setImageResource(R.drawable.ic_bottom_navigation_home)
            binding.tvMainBottomNavHome.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_gray))
            binding.btnMainBottomNavStorage.setImageResource(R.drawable.ic_bottom_navigation_storage_selected)
            binding.tvMainBottomNavStorage.setTextColor(ContextCompat.getColor(SparkApplication.ApplicationContext(), R.color.spark_dark_pinkred))
            binding.fabHomeMain.visibility = View.GONE
            binding.fabHomeMakeRoom.visibility = View.GONE
            binding.fabHomeJoinCode.visibility = View.GONE
        }
    }

    private fun initFloatingButtonClickListener() {
        binding.fabHomeMain.setOnClickListener(){
            FloatingAnimationUtil.toggleFab(binding.fabHomeMain,binding.fabHomeMakeRoom,binding.fabHomeJoinCode,binding.layoutMainFabBackground,fabState)
            fabState = !fabState
            initBindingVariable()
        }
    }

    private fun initBindingVariable(){
        binding.fabState = fabState
    }

}

보면 리스너 달아주는 부분이 같은 코드들이 계속해서 나오고 굉장히 더럽다

 

 

이걸한번 해결해보자면

아이디어는 우선 뷰모델에 라이브데이터로 변수하나 만들어놓고 거기에 1,2,3, 값을 넣어서 경우의 수를 구분한다

 

그리고 데이터 바인딩으로 onclick에 라이브데이터를 1,2,3으로 바꿔주는 함수를 맞는 경우의수로 맞춰준다.

 

다음 각 바꾸고싶었던 src,color 같은걸 2가지 선택지 를 고르는것을 라이브데이터를 기준으로 삼항연산자로 설정한다.

 

그러면 클릭에 따라서 원하는 속성들이 바뀌는 로직이 아주 간결하게xml사이로 녹아들게된다.

 

그리고 fragment Transaction 하는 코드는 따로 옵저버를 통해서 라이브데이터가 1,2,3, 을 바뀔때마다 보고 when문으로 분기해서 처리해준다.

 

 

이제 코드를 봐보자

1. MainViewModel

package com.spark.android.ui.main.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel : ViewModel() {
    private val _tabPosition = MutableLiveData(TAB_FEED)
    val tabPosition: LiveData<Int> = _tabPosition

    fun initTabPositionFeed() {
        _tabPosition.value = TAB_FEED
    }

    fun initTabPositionHome() {
        _tabPosition.value = TAB_HOME
    }

    fun initTabPositionStorage() {
        _tabPosition.value = TAB_STORAGE
    }

    companion object {
        const val TAB_FEED = 0
        const val TAB_HOME = 1
        const val TAB_STORAGE = 2
    }
}

이렇게 1,2,3을 라이브데이터에 바꿔주는 함수들을 만든다.

 

2.xml에 리스너 붙여주고 각 바꾸려했던것 3항연산자로 조건에 따라 설정해준다.

<TextView
    android:id="@+id/tv_main_bottom_nav_storage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="26dp"
    android:includeFontPadding="false"
    android:text="@string/main_bottom_nav_storage"
    android:textSize="12dp"
    app:layout_constraintBottom_toBottomOf="@+id/layout_main_bottom_nav"
    app:layout_constraintEnd_toEndOf="@+id/btn_main_bottom_nav_storage"
    app:layout_constraintStart_toStartOf="@+id/btn_main_bottom_nav_storage"
    tools:textColor="@{mainViewModel.tabPosition == 2 ? @color/spark_dark_pinkred : @color/spark_dark_gray }" />

<ImageButton
    android:id="@+id/btn_main_bottom_nav_storage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="56dp"
    android:background="@android:color/transparent"
    android:onClick="@{()->mainViewModel.initTabPositionStorage()}"
    android:padding="12dp"
    android:src="@{mainViewModel.tabPosition == 2 ? @drawable/ic_bottom_navigation_storage_selected :@drawable/ic_bottom_navigation_storage}"
    app:layout_constraintStart_toEndOf="@+id/btn_main_bottom_nav_home"
    app:layout_constraintTop_toTopOf="@+id/btn_main_bottom_nav_home"
    tools:src="@drawable/ic_bottom_navigation_storage" />
    
  <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_home_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="28dp"
            android:layout_marginBottom="28dp"
            android:backgroundTint="@{fabState ? @color/spark_white : @color/spark_dark_pinkred}"
            android:src="@drawable/ic_fab_plus"
            android:visibility="@{mainViewModel.tabPosition == 1 ? View.VISIBLE : View.GONE }"
            app:borderWidth="0dp"
            app:fabCustomSize="60dp"
            app:layout_constraintBottom_toTopOf="@+id/layout_main_bottom_nav"
            app:layout_constraintEnd_toEndOf="parent"
            tools:backgroundTint="@color/spark_dark_pinkred" />

예시로 하나씩만 추가했다 보면 버튼 눌릴때 onclick로 뷰모델에 변수 수정해주는 리스너를 붙여서 바뀌게하고

 

그 1,2,3을 기준으로 색깔,src,visibility를 분기처리해준다.

 

3.fragment transaction의 경우는 xml내에서 처리 못하기에 observer달아서처리

package com.spark.android.ui.main

import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import com.spark.android.R
import com.spark.android.SparkApplication
import com.spark.android.databinding.ActivityMainBinding
import com.spark.android.ui.base.BaseActivity
import com.spark.android.ui.home.FeedFragment
import com.spark.android.ui.home.HomeMainFragment
import com.spark.android.ui.home.StorageFragment
import com.spark.android.ui.main.viewmodel.MainViewModel
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_FEED
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_HOME
import com.spark.android.ui.main.viewmodel.MainViewModel.Companion.TAB_STORAGE
import com.spark.android.util.FloatingAnimationUtil
import com.spark.android.util.initStatusBarColor
import com.spark.android.util.initStatusBarTextColorToWhite

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {

    private val mainViewModel by viewModels<MainViewModel>()
    private lateinit var feedFragment: FeedFragment
    private lateinit var homeMainFragment: HomeMainFragment
    private lateinit var storageFragment: StorageFragment
    private var fabState = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.mainViewModel = mainViewModel
        initStatusBarStyle()
        initTransactionEvent()
        initBindingVariable()
        initFloatingButtonClickListener()
        initTabPositionObserver()
    }

    private fun initStatusBarStyle() {
        initStatusBarColor(R.color.spark_white)
        initStatusBarTextColorToWhite()
    }

    private fun initTransactionEvent() {
        feedFragment = FeedFragment()
        homeMainFragment = HomeMainFragment()
        storageFragment = StorageFragment()
        supportFragmentManager.beginTransaction().add(R.id.container_main, homeMainFragment)
            .commit()
    }

    private fun initTabPositionObserver() {
        mainViewModel.tabPosition.observe(this) { position ->
            when (position) {
                TAB_FEED -> supportFragmentManager.beginTransaction()
                    .replace(R.id.container_main, feedFragment).commit()
                TAB_HOME -> supportFragmentManager.beginTransaction()
                    .replace(R.id.container_main, homeMainFragment).commit()
                TAB_STORAGE -> supportFragmentManager.beginTransaction()
                    .replace(R.id.container_main, storageFragment).commit()
            }
        }
    }


    private fun initFloatingButtonClickListener() {
        binding.fabHomeMain.setOnClickListener() {
            FloatingAnimationUtil.toggleFab(
                binding.fabHomeMain,
                binding.fabHomeMakeRoom,
                binding.fabHomeJoinCode,
                binding.layoutMainFabBackground,
                fabState
            )
            fabState = !fabState
            initBindingVariable()
        }
    }

    private fun initBindingVariable() {
        binding.fabState = fabState
    }

}

initTabPositionObserver 를 보면 뷰모델에 있는 변수에 옵져버 붙여놓고 그거에 바뀌었을때 적용시킬 코드를 작성해서 처리했다

 

그래서 결론은 분기처리의 경우 경우의수는 라이브데이터로 변수하나파놓고 onclick로 변수바꾸는 형태이며

각각의 분기처리는 삼항연산자,옵져버,바인딩어댑터로 처리할수있다