์ฝํํธ๋ ์ฆ ์ฌ์ฉ์๋ฅผ ์ํ ๋ง์ถค ๋ ์ฆ ์ถ์ฒ ๋ฐ ์คํ๋ผ์ธ ํฝ์ ์์ฝ ์๋น์ค
๊ตญ๋ด ๋ชจ๋ ๋ ์ฆ ์ ๋ณด, ๋ฆฌ๋ทฐ ๋ถํฐ ์๋ฃ ์ปค๋ฎค๋ํฐ์ ์คํ๋ผ์ธ ํฝ์ ์์ฝ ๊น์ง! "์ค๋ ๋ฌด์จ ๋ ์ฆ๋ผ์ง?" ๊ณ ๋ฏผ๋ ๋, ์ค๋ฌด๋ !
SOPT 28th APPJAM
ํ๋ก์ ํธ ๊ธฐ๊ฐ: 2021.06.26 ~ 2021.07.17
Splash | ์นด์นด์คํก ๋ก๊ทธ์ธ |
์จ๋ณด๋ฉ1,2 | ์จ๋ณด๋ฉ3 | ์จ๋ณด๋ฉ4 |
ํ | ๋ฐ๊ฒฌ | ์ ํ ์์ธ |
์ต๊ทผ ๊ฒ์ | ํํฐ ๊ฒ์ |
-
์นด์นด์คํก์ ์ด์ฉํ์ฌ ์์ ๋ก๊ทธ์ธ์ ํฉ๋๋ค.
โจShow Detailsโจ
โพ Kakaotalk Login
๐งพ LoginViewModel.kt
- ๋จ๋ง ๋ก๊ทธ์ธ ์ํ ํ์ธ
fun newKakao(context:Context){ if (AuthApiClient.instance.hasToken()) { //๋ก๊ทธ์ธ์ด ๋ ์ํ์ธ์ง ํ์ธ UserApiClient.instance.accessTokenInfo { tokenInfo, error -> //์๋ฒ์ ์ ํจํ accessํ ํฐ์ด ์๋์ง ๊ฐ์ ธ์ด //ํ์ฌ ์ ํจํ accessํ ํฐ์ด ์์ //accessํ ํฐ์ด ๋ง๋ฃ๋ ๊ฒ์ด๋ผ๋ฉด sdk๋ด๋ถ์์ accesstoken์ ๊ฐฑ์ ํ๋ค. if (error != null) { if (error is KakaoSdkError && error.isInvalidTokenError()) { //accessํ ํฐ ๊ฐฑ์ ๊น์ง ์คํจํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ refreshํ ํฐ์ด ์ ํจํ์ง ์์, ๋ก๊ทธ์ธ ํ์ newKakaoLogin(context) } else { //๊ธฐํ ์๋ฌ } } else{ //ํ ํฐ ์ ํจ์ฑ ์ฒดํฌ ์ฑ๊ณต(ํ์ ์ sdk๋ด๋ถ์์ ํ ํฐ ๊ฐฑ์ ๋จ) newKakaoLogin(context) } } } else { //๋จ๋ง์ ํ ํฐ์ด ์์ผ๋ ๋ก๊ทธ์ธ ํ์ newKakaoLogin(context) } }
- ์นด์นด์คํก ์ค์น ์ฌ๋ถ ํ์ธ ํ ๋ก๊ทธ์ธ ์ฐฝ์ผ๋ก ์ด๋
fun newKakaoLogin(context: Context){ val callback: (OAuthToken?, Throwable?) -> Unit = { token, error -> if (error != null) { when { } } else if (token != null) { //Toast.makeText(context, "๋ก๊ทธ์ธ์ ์ฑ๊ณตํ์์ต๋๋ค.", Toast.LENGTH_SHORT).show() getKakaoInfo() } } if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { Log.e(LOGINVIEWMODEL, "์นด์นด์คํก์ผ๋ก") UserApiClient.instance.loginWithKakaoTalk(context, callback = callback) } else { Log.e(LOGINVIEWMODEL, "ํํ์ด์ง๋ก") UserApiClient.instance.loginWithKakaoAccount(context, callback = callback) } }
- ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ
fun getKakaoInfo(){ // ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ (๊ธฐ๋ณธ) UserApiClient.instance.me { user, error -> if (error != null) { Log.e("LOGINVIEWMODEL", "์ฌ์ฉ์ ์ ๋ณด ์์ฒญ ์คํจ / "+error.toString(), error) } else if (user != null) { Log.i("LOGINVIEWMODEL_RESULT", "์ฌ์ฉ์ ์ ๋ณด ์์ฒญ ์ฑ๊ณต" + "\nํ์๋ฒํธ: ${user.id}" + "\n๋๋ค์: ${user.kakaoAccount?.profile?.nickname}" + "\nํ๋กํ์ฌ์ง: ${user.kakaoAccount?.profile?.thumbnailImageUrl}") kakaoUser.name = user.kakaoAccount?.profile?.nickname.toString() kakaoUser.oauthKey = user.id.toString() Log.d("LOGINVIEWMODEL_RESULT","${kakaoUser.oauthKey} + ${kakaoUser.name}") //์๋ฒ์ ์์ฒญ postLogin() } } }
- ์๋ฒ์ ์ฌ์ฉ์ ์ ๋ณด ์ ์ก ๋ฐ ์๋ ๋ก๊ทธ์ธ ์ํ sharedpreference ์ค์ ์ ์งํํฉ๋๋ค. (splashํ๋ฉด์์ ๋ก๊ทธ์ธ ๋ด์ญ์ด ์กด์ฌํ๋ฉด ๋ฐ๋ก homeActivity๋ก, ์๋๋ฉด loginActivity๋ก intent)
fun postLogin(){ Log.d("LOGIN","post${kakaoUser.oauthKey} + ${kakaoUser.name}") val requestLoginData = RequestLoginData(oauthKey = kakaoUser.oauthKey, name = kakaoUser.name) //์ ์กํ ๋ฐ์ดํฐ val call: Call<ResponseLoginData> = UserClient.getApi.postLogin(requestLoginData) call.enqueue(object : Callback<ResponseLoginData> { override fun onResponse( call: Call<ResponseLoginData>, response: Response<ResponseLoginData> ){ //token๊ฐ ์ ์ฅ SharedPreferenceToken.putSettingItem(getApplication<Application>().applicationContext,"USER_TOKEN",response.body()?.accessToken.toString()) isNew.value = response.body()?.isNewUser } override fun onFailure(call: Call<ResponseLoginData>, t: Throwable) { Log.d("NetworkTest","error:$t") } }) }
-
์ฌ์ฉ์ ๋ง์ถค ํ๋ ์ด์ ์ ์ ๊ณตํ๊ธฐ ์ํด ์จ๋ณด๋ฉ ๊ณผ์ ์ ์ํํฉ๋๋ค.
โจShow Detailsโจ
โพ Onboarding
๐งพ OnboardDatabase.kt
- onboardData๋ผ๋ ๊ฐ์ฒด๋ฅผ singletone์ผ๋ก ์์ฑํ์ฌ 4๊ฐ์ fragment์์ ํ ๊ฐ์ฒด๋ฅผ ๊ณต์ ํ๋๋ก ํ์์ต๋๋ค.
(๊ฐ ํ๋ฉด์์ ์ป์ ์ ๋ณด๋ค์ ํ ๊ฐ์ฒด์ ๋ฃ์ด ์๋ฒ ์ ๋ฌ)
class OnboardDatabase { //์ฑ๊ธํค ๊ฐ์ฒด ์์ฑ companion object{ lateinit var onboardData:OnboardData } fun getOnboardData():OnboardData{ return onboardData } //... }
โพ ๊ฐ fragment์์ recyclerView๋ฅผ ์ด์ฉํ์ฌ ๋ฒํผ์ ๊ตฌ์ฑํ์์ต๋๋ค.
- RecyclerView SingleChoice.
: ๊ฐ recyclerView์ adapter์ single choice๋ฅผ ์ํ Interface๋ฅผ ์ ์ํ ํ fragment์์ ํด๋นํ๋ setOnClickListener๋ฅผ ๋ฌ์ ์ฌ์ฉํฉ๋๋ค.
๐งพ AgeAdapter.kt
class AgeAdapter : RecyclerView.Adapter<AgeAdapter.MyViewHolder>() { val ageList = mutableListOf<AgeInfo>() override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): MyViewHolder { val binding = ItemOnboardTextBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return MyViewHolder(binding) } override fun getItemCount(): Int = ageList.size override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.onBind(ageList[position]) holder.itemView.setOnClickListener { itemClickListener.onClick(it, position) } } // (2) ๋ฆฌ์ค๋ ์ธํฐํ์ด์ค interface OnItemClickListener { fun onClick(v: View, position: Int) } // (3) ์ธ๋ถ์์ ํด๋ฆญ ์ ์ด๋ฒคํธ ์ค์ fun setItemClickListener(onItemClickListener: OnItemClickListener) { this.itemClickListener = onItemClickListener } // (4) setItemClickListener๋ก ์ค์ ํ ํจ์ ์คํ private lateinit var itemClickListener : OnItemClickListener class MyViewHolder( private val binding: ItemOnboardTextBinding ) : RecyclerView.ViewHolder(binding.root) { fun onBind(ageInfo: AgeInfo) { binding.tvText.text = ageInfo.age } } }
๐งพ OneOnboardFragment.kt
private fun singleChoice() { binding.rvGender.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener { override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { if (e.action == MotionEvent.ACTION_MOVE) { } else viewModel.genderSingleChoice(rv,e) return false } override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } })
- onboardData๋ผ๋ ๊ฐ์ฒด๋ฅผ singletone์ผ๋ก ์์ฑํ์ฌ 4๊ฐ์ fragment์์ ํ ๊ฐ์ฒด๋ฅผ ๊ณต์ ํ๋๋ก ํ์์ต๋๋ค.
-
์ฌ์ฉ์ ๋ง์ถค ํ๋ ์ด์ , ์ด๋ฒคํธ, ์์ ๋ฑ์ ๊ฐ๋ตํ ๋ชจ์๋ณผ ์ ์์ต๋๋ค.
โจShow Detailsโจ
โพ Home ํ๋ฉด
-
์ ์ฅ๋์ด์๋ ์ฌ์ฉ์์ ํ ํฐ์ ์ด์ฉํด viewModel์์ ์๋ฒ ํต์ , ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ ๋ณด์ ๋ํ ๋ง์ถค ์ ๋ณด๋ฅผ ๋ด์ ๋ ์ฆ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ฐ RecommendationBySeason, RecommendationBySituation, RecommendationByUser, Giudes, DeadlineEvent, LastestEvent, NewLens๋ผ๋ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ก ๋ฐ์, ์ด๋ฅผ RecyclerView๋ก ๊ตฌ์ฑํด ๋ณด์ฌ์ฃผ์์ต๋๋ค.
-
์ด๋ viewModel์์ ํต์ ํด ๋ฐ์ ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ, fragment์์ observe๋ฅผ ํตํด ๊ด์ฐฐํ๊ณ ์๋ค๊ฐ, ๋ฐ์ดํฐ์ ๋ณํ๊ฐ ์๊ธธ ๊ฒฝ์ฐ ์ด๋ฅผ ์๋ ค์ฃผ์ด ์ ๋ฐ์ดํธ๋ฅผ ์งํํฉ๋๋ค.
-
์์ ๋ ์ฆ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ค ์ค์์ otherColors๋ผ๋ ์๊น ๋ฐฐ์ด์ ๋ฐ๋ ๊ฐ์ฒด์ ๊ฒฝ์ฐ, ์ค์ฒฉ recyclerView๋ก ํํ. _ ์ด๋ ์ธ๋ถ RecyclerView_ Adapter์ ViewHolder์์ bind ์ ๋ด๋ถ RecyclerView์ Adapter๋ฅผ ์ค์ ํจ์ผ๋ก์ ๊ตฌํ.
๐งพ CuratingListAdapter.kt
class CuratingListAdapter:RecyclerView.Adapter<CuratingListAdapter.CuratingViewHolder>() { private var curateList = emptyList<RecommendationByUser>() class CuratingViewHolder( private val binding : ItemOneCuratingBinding ): RecyclerView.ViewHolder(binding.root){ fun bind(curatingInfo: RecommendationByUser){ binding.curatingInfo = curatingInfo //์์ RecyclerView Adapter ์ค์ val listForColor = LensColorListAdapter() listForColor.setColoring(curatingInfo.otherColorList as List<String>) binding.rvOneCuratingColor.adapter = listForColor //์ ์ ํ _ recycler view์ ๊ฒฝ์ฐ๋ ์ฌ๊ธฐ์ ํด๋ฆญ ๋ฆฌ์ค๋ ์ค์ } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CuratingViewHolder { val binding = ItemOneCuratingBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return CuratingViewHolder(binding) } override fun onBindViewHolder(holder: CuratingViewHolder, position: Int) { holder.bind(curateList[position]) holder.itemView.setOnClickListener { itemClickListener.onClick(it, position) } } override fun getItemCount(): Int = curateList.size fun setCurating(curateList : List<RecommendationByUser>){ this.curateList = curateList notifyDataSetChanged() } // (2) ๋ฆฌ์ค๋ ์ธํฐํ์ด์ค interface OnItemClickListener { fun onClick(v: View, position: Int) } // (3) ์ธ๋ถ์์ ํด๋ฆญ ์ ์ด๋ฒคํธ ์ค์ fun setItemClickListener(onItemClickListener: OnItemClickListener) { this.itemClickListener = onItemClickListener } // (4) setItemClickListener๋ก ์ค์ ํ ํจ์ ์คํ private lateinit var itemClickListener : OnItemClickListener }
-
OneHomeFragment์์ ๊ฐ ์์ ํด๋ฆญ ์ ...
-
RecommendationBySeason, RecommendationBySituation, RecommendationByUser ์ ๊ฒฝ์ฐ, RecyclerView์ item ํด๋ฆญ ์ ํด๋น ๋ ์ฆ์ ์์ธ ํ์ด์ง๋ก ์ด๋. _ ๋ ์ฆ์ ์ํ id๋ฅผ ๋๊ฒจ์ค.
-
๊ฐ RecyclerView ์์ ์๋ '๋๋ณด๊ธฐ>' ํด๋ฆญ ์ ๋ฐ๊ฒฌ์ ๊ด๋ จ ํญ์ผ๋ก ์ด๋. _ ๊ณ์ ๊ด๋ จ ์์ดํ ์ถ์ฒ์ ๋๋ณด๊ธฐ๋ฅผ ํด๋ฆญ ์ ๋ฐ๊ฒฌ ํญ์ 4๋ฒ์งธ ํญ์ธ ๊ณ์ ํญ์ผ๋ก ์ด๋.
-
์๋จ์ ๊ฒ์๋ฐ ํด๋ฆญ ์ ๊ฒ์ ํ์ด์ง๋ก ์ด๋.
๐งพ OneHomeFragment.kt
class OneHomeFragment : Fragment() { private val handler: Handler = Handler(Looper.getMainLooper()) private var _binding: FragmentHomeOneBinding? = null private val binding get() = _binding ?: error("View๋ฅผ ์ฐธ์กฐํ๊ธฐ ์ํด binding์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.") private val oneHomeViewModel: OneHomeViewModel by activityViewModels() private lateinit var situLayoutManager : RecyclerView.LayoutManager private lateinit var seasonLayoutManager : RecyclerView.LayoutManager override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentHomeOneBinding.inflate(inflater, container, false) binding.lifecycleOwner = viewLifecycleOwner initLayout() setClickListener() oneHomeViewModel.getHome() setCuratingAdapter() setCuratingObserve() setRecommend1Adapter() setRecommend1Observe() setRecommend2Adapter() setRecommend2Observe() setEventAdapter() setEventObserve() setEventIndicator() setAdAdapter() setAdObserve() setAdIndicator() setTipAdapter() setTipObserve() setNewAdapter() setNewObserve() return binding.root } override fun onStart() { super.onStart() oneHomeViewModel.situation.observe(viewLifecycleOwner) { if(oneHomeViewModel.situation.value.equals("์ผ์")) { binding.tvHomeRecommend.text = oneHomeViewModel.situation.value + "์์ ๋ผ์ง ์ข์ ๋ ์ฆ" } else { binding.tvHomeRecommend.text = oneHomeViewModel.situation.value + "ํ ๋ ๋ผ์ง ์ข์ ๋ ์ฆ" } } oneHomeViewModel.userName.observe(viewLifecycleOwner) { binding.tvHomeCurating.text = oneHomeViewModel.userName.value + "๋ ์ด ๋ ์ฆ ์ด๋ ์ธ์?" } } //RecyclerView ์์ดํ ์ฌ์ด ๋ง์ง ์ง์ ๊ด๋ จ ์ฝ๋ ์๋ต... private fun setCuratingAdapter(){ val curatingListAdapter = CuratingListAdapter() curatingListAdapter.setItemClickListener(object: CuratingListAdapter.OnItemClickListener{ override fun onClick(v: View, position: Int) { val rbu :RecommendationByUser = oneHomeViewModel.recommendationByUserList.get(position) val intent = Intent(requireContext(), DetailActivity::class.java) intent.putExtra("itemId", rbu.id) startActivity(intent) } }) binding.rvHomeCurating.adapter = curatingListAdapter } private fun setCuratingObserve(){ oneHomeViewModel.recommendationByUserList.observe(viewLifecycleOwner){ curatingList -> with(binding.rvHomeCurating.adapter as CuratingListAdapter){ setCurating(curatingList) } } } private fun setEventAdapter(){ binding.vpHomeEvent.adapter = EventViewPagerAdapter() } private fun setEventObserve(){ oneHomeViewModel.deadlineEventList.observe(viewLifecycleOwner){ eventList -> with(binding.vpHomeEvent.adapter as EventViewPagerAdapter){ setEvent(eventList) } } } private fun setEventIndicator() { TabLayoutMediator(binding.tabHomeEvent, binding.vpHomeEvent) { tab, position -> }.attach() } //์ด ์ธ 5๊ฐ์ ๋ฐ์ดํฐ ๊ฐ์ฒด์ ๋ํ RecyclerView์ Adapter์ Observe ์ฝ๋ ์๋ต. private fun setClickListener(){ binding.tvOneSearch.setOnClickListener { val intent = Intent(context, SearchActivity::class.java) startActivity(intent) } binding.clHomeCuratingMore.setOnClickListener{ activity?.supportFragmentManager ?.beginTransaction() ?.replace(R.id.nav_host_home, TwoHomeFragment() .apply { arguments = Bundle().apply { putInt("setIdx", 1) } }, "home->foryou") ?.commit() (activity as HomeActivity).setBottomChecked(1) } binding.clHomeRecommendMore.setOnClickListener{ activity?.supportFragmentManager ?.beginTransaction() ?.replace(R.id.nav_host_home, TwoHomeFragment().apply { arguments = Bundle().apply { putInt("setIdx", 2) } },"home->situ") ?.commit() (activity as HomeActivity).setBottomChecked(1) } binding.clHomeSeasonMore.setOnClickListener{ activity?.supportFragmentManager ?.beginTransaction() ?.replace(R.id.nav_host_home, TwoHomeFragment().apply { arguments = Bundle().apply { putInt("setIdx", 4) } }, "home->saeson") ?.commit(); (activity as HomeActivity).setBottomChecked(1) } binding.clHomeNewMore.setOnClickListener { activity?.supportFragmentManager ?.beginTransaction() ?.replace(R.id.nav_host_home, TwoHomeFragment().apply { arguments = Bundle().apply { putInt("setIdx", 3) } }, "home->saeson") ?.commit(); (activity as HomeActivity).setBottomChecked(1) } } }
-
-
์ฌ์ฉ์ ๋ง์ถค ํ๋ ์ด์ ์ ํ๋์ ๋ชจ์๋ณผ ์ ์์ต๋๋ค.
โจShow Detailsโจ
โพ ๊ธฐ๋ณธ์ ์ธ ๊ตฌํ ๋ฐฉ์์ Home์์ RecyclerView๋ฅผ ์ด์ฉํ์ฌ ๊ตฌํํ ๊ฒ๊ณผ ํฌ๊ฒ ์ฐจ์ด๋ ์์ต๋๋ค. ์ฌ์ฉ์ ํ ํฐ์ ์ฌ์ฉํ์ฌ _ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ฌ ๊ฒฝ์ฐ, ํด๋น ๋ฐ์ดํฐ๋ฅผ ๊ฐ ํญ์์ ํ ๋ง์ ๋ง๊ฒ RecyclerView๋ฅผ ์ด์ฉํ์ฌ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค๋๋ค. ์๊น์ ์ค์ฒฉ recyclerView๋ฅผ ์ด์ฉํ์๊ณ , ๊ฐ ์์ดํ ์ ํด๋ฆญ ์ ์์ธ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค. ๊ฒ์๋ฐ๋ฅผ ํด๋ฆญ ์ ๊ฒ์ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
-
์ฐจ์ด์ : ๋ฐ๊ฒฌ fragment ์์ ๋ค์ 4๊ฐ์ fragment๋ฅผ tabLayout๊ณผ viewPager2๋ฅผ ์ด์ฉํ ํญ์ด ์ฌ๋ผ๊ฐ์ง. ์ด๋ฅผ ํตํด ๋ฐ๊ฒฌ ํญ์์๋ ๋ค์ ์์ธ 4๊ฐ์ ํญ์ด ๋ณด์ฌ์ง๋ฉฐ, ์ด๋ฅผ ์ค์์ดํ๋ฅผ ํตํด ์ด๋ํ ์ ์์.
-
๊ฐ ์์ธ ํญ์ For you, ๊ณ์ , ์ํฉ, ์ ์ ํ ์ ๋ณด๋ฅผ onBoarding ๊ณผ์ ์์ ์ ๋ ฅํ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ณด์ฌ์ค๋๋ค. ๋ํ, ๊ฐ ํญ์๋ ํน์ ์์ด์ฝ ํด๋ฆญ ์ ํด๋น ํญ์ ์ ๋ณด๋ฅผ ์๋ ค์ฃผ๋ ๋ค์ด์ผ๋ก๊ทธ์, ์ ๋ ฌ ๊ด๋ จ ๋ค์ด์ผ๋ก๊ทธ๊ฐ ์์ต๋๋ค. _ ์ ๋ ฌ ํด๋ฆญ ์ ๊ฐ๊ฒฉ์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ๋จ. (viewmodel์์ recyclerview์ ์ ์ฉ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์๋ฒํต์ ๋ฐ๊ณ , ์ด๋ฅผ observe๊ฐ ๊ด์ฐฐํ๋ค ์ ์ฉ)
-
๋ฐ๊ฒฌ ํญ์ ๋ก๊ทธ ํด๋ฆญ ์ ํ์ผ๋ก ์ด๋.
โพ
๐งพ TwoHomeFragment.kt
class TwoHomeFragment : Fragment() { private var _binding: FragmentHomeTwoBinding? = null private val binding get() = _binding ?: error("View๋ฅผ ์ฐธ์กฐํ๊ธฐ ์ํด binding์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.") private val homeViewModel: TwoHomeViewModel by viewModels() //์์์ด๊ธฐํ private lateinit var mContext: Context private var idx : Int? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentHomeTwoBinding.inflate(inflater, container, false) binding.lifecycleOwner = viewLifecycleOwner mContext = requireContext() setClickListener()
-
โ
homeViewModel.getSuggestData()
return binding.root
}
โ
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
//ViewPager2์ tabLayout Init
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val pagerAdapter = PagerFragmentStateAdapter(requireActivity())
pagerAdapter.addFragment(TwoHomeForYouFragment())
pagerAdapter.addFragment(TwoHomeSituFragment())
pagerAdapter.addFragment(TwoHomeNewFragment())
pagerAdapter.addFragment(TwoHomeSeasonFragment())
binding.vpHomeTwo.adapter = pagerAdapter
binding.vpHomeTwo.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
}
})
TabLayoutMediator(binding.findTabLayout, binding.vpHomeTwo) { tab, position ->
when (position) {
0 -> {
homeViewModel.tabItem2.observe(viewLifecycleOwner) {
tab.text = homeViewModel.tabItem1
}
}
1 -> {
homeViewModel.tabItem2.observe(viewLifecycleOwner){
tab.text = homeViewModel.tabItem2.value
}
}
2 -> {
homeViewModel.tabItem2.observe(viewLifecycleOwner) {
tab.text = homeViewModel.tabItem3
}
}
3 -> {
homeViewModel.tabItem4.observe(viewLifecycleOwner){
tab.text = homeViewModel.tabItem4.value
}
}
}.attach()
โ
idx = arguments?.getInt("setIdx")
if(idx != null) {
val tabLayout = binding.findTabLayout
val tab = tabLayout.getTabAt(idx!! - 1)
tab!!.select()
binding.vpHomeTwo.setCurrentItem(idx!! - 1, false)
}
}
private fun setClickListener() {
binding.tvTwoSearch.setOnClickListener {
val intent = Intent(context, SearchActivity::class.java)
startActivity(intent)
}
binding.ivTwoLogo.setOnClickListener{
activity?.supportFragmentManager
?.beginTransaction()
?.replace(
R.id.nav_host_home, OneHomeFragment(), "home->foryou")
?.commit()
(activity as HomeActivity).setBottomChecked(0)
}
}
}
```
โ ๐งพ PagerFragmentAdapter.kt _ viewPagerํ fragment๋ฅผ ์ง์ .
```kotlin
class PagerFragmentStateAdapter(fragmentActivity: FragmentActivity): FragmentStateAdapter(fragmentActivity) {
var fragments : ArrayList<Fragment> = ArrayList()
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
fun addFragment(fragment: Fragment) {
fragments.add(fragment)
notifyItemInserted(fragments.size-1)
}
fun removeFragment() {
fragments.removeLast()
notifyItemRemoved(fragments.size)
}
}
```
๐งพ TwoHomeForYouFragment.kt
```kotlin
class TwoHomeForYouFragment : Fragment() {
companion object {
fun newInstance() = TwoHomeForYouFragment()
}
private var _binding: FragmentHomeTwoForyouBinding? = null
private val binding get() = _binding ?: error("View๋ฅผ ์ฐธ์กฐํ๊ธฐ ์ํด binding์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.")
private val viewModel: TwoHomeViewModel by activityViewModels()
private val fragmentViewModel: TwoHomeForYouViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHomeTwoForyouBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
//๋ฐ์ดํฐ setting
setForYouAdapter()
setForYouObserve()
โ
//์ ๋ ฌ ํด๋ฆญ ์
binding.ivForYouSort.setOnClickListener{
val findSortPriceFragment = FindSortPriceFragment()
findSortPriceFragment.setButtonClickListener(object: FindSortPriceFragment.OnButtonClickListener {
override fun onLowPriceClicked() {
//์ฌ๊ธฐ์ ์ ๋ ฌ
Log.d("click", "low price")
viewModel.getForyou(1,"price","asc")
}
override fun onHighPriceClicked() {
// ์ฌ๊ธฐ์ ์ ๋ ฌ
Log.d("click", "high price")
viewModel.getForyou(1,"price","desc")
}
})
findSortPriceFragment.show(childFragmentManager, "CustomDialog")
}
binding.ivFindQuestion1.setOnClickListener{
val findQuestionFragment = FindQuestionFragment(1)
findQuestionFragment.show(childFragmentManager, "CustomDialog2")
}
return binding.root
}
//... ์๋๋ ํ์ OneHomeFragment.kt์ ์ ์ฌ.
}
```
<br>
</div>
</details>
-
์ํ์ ์์ธ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค.
โจShow Detailsโจ
โพ ViewPager2
- ์ด๋ฏธ์ง ์ค์์ดํ ์ ํ์ ์ํด ViewPager2๋ฅผ ์ฌ์ฉ
โพ DotsIndicator
- TabLayout์ Indicator custom
โพ ViewPager2 - ์ด๋ฏธ์ง ์ค์์ดํ ์ ํ์ ์ํด ViewPager2๋ฅผ ์ฌ์ฉ
๐งพ UserClient.kt
data class KakaoUser( var oauthKey: String, var name: String )
โพ DotsIndicator - TabLayout์ Indicator custom
๐งพ UserClient.kt
data class KakaoUser( var oauthKey: String, var name: String )
-
๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค.
-
ํค์๋๋ฅผ ์ด์ฉํ์ฌ ๊ฒ์์ ์ํํฉ๋๋ค.
-
๋ธ๋๋, ์ปฌ๋ฌ, ์ง๊ฒฝ, ์ฃผ๊ธฐ๋ฅผ ์ด์ฉํ์ฌ ํํฐ ๊ฒ์์ ์ํํฉ๋๋ค.
โจShow Detailsโจ
โพ ํค์๋ ๊ฒ์์ ๊ฒฝ์ฐ ๋ถ๋ชจ activity์์ ์ ๋ ฅ๋ฐ์ ํค์๋๋ฅผ ์์ fragment์์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
๋ฐ๋ผ์ fragment๋ค์์ activity์ viewModel์ ๊ณต์ ํ์ฌ ์ฌ์ฉํ๊ธฐ ์ํด ์๋์ ๊ฐ์ด ๋ทฐ๋ชจ๋ธ์ ์ ์ํฉ๋๋ค.
๐งพ OneSearchFragment.ktprivate val viewModel: SearchViewModel by activityViewModels()
โพ ์ต๊ทผ ๊ฒ์์ด ์ถ๊ฐ๋ฅผ ์ํด sharedPreference๋ฅผ ์ฌ์ฉํฉ๋๋ค.
(ํ ์ฝ๋์ ๊ฒฝ์ฐ mutableList๋ฅผ sharedPreference์ ๋ฃ๋ ์ค๋ฅ๋ฅผ ๋ฒํ๊ณ ์์ต๋๋ค. ์ด๋ ๊ณ ์ณ์ ธ์ผ ํ ์ฝ๋ ํจํด์ ๋๋ค.)
๐งพ SharedPreferences.ktobject SharedPreferences { fun setStringArrayPref(context: Context, key: String, values: MutableList<RecentInfo>) { val prefs = context.getSharedPreferences("setting",Context.MODE_PRIVATE) val editor = prefs.edit() val a = JSONArray() for (i in 0 until values.size) { a.put(values[i].name) } if (values.isNotEmpty()) { editor.putString(key, a.toString()) } else { editor.putString(key, null) } editor.apply() } fun getStringArrayPref(context: Context, key: String): ArrayList<String>? { val prefs = context.getSharedPreferences("setting",Context.MODE_PRIVATE) val json = prefs.getString(key, null) val urls = ArrayList<String>() if (json != null) { try { val a = JSONArray(json) for (i in 0 until a.length()) { val url = a.optString(i) urls.add(url) } } catch (e: JSONException) { e.printStackTrace() } } return urls } }
๐งพ SearchViewModel.kt
fun updateRecent(context:Context, recentSearch: MutableList<RecentInfo>, recentAdapter: RecentAdapter) { //sharedPreference SharedPreferences.setStringArrayPref(context,"RECENT_KEY",recentSearch) recentAdapter.recentList.clear() recentAdapter.recentList.addAll(recentSearch) recentAdapter.notifyDataSetChanged() }
โพ ํํฐ ๊ฒ์์ ๊ฒฝ์ฐ๋ ์จ๋ณด๋ฉ๊ณผ ๋์ผํ๊ฒ ์ ๋ณด๋ฅผ ํ ๊ฐ์ฒด์ ๋ชจ์ผ๊ธฐ ์ํด SearchDatabase๋ฅผ ์์ฑํ์ฌ ๊ด๋ฆฌ ํ ์๋ฒ๋ก ์ ์กํฉ๋๋ค. ๐งพ SearchDatabase.kt
class SearchDatabase { //์ฑ๊ธํค ๊ฐ์ฒด ์์ฑ companion object{ lateinit var searchData:SearchData }
Architecture | MVVM |
Jetpack Components | DataBinding, LiveData, ViewModel, Lifecycle |
Login | Kakaotalk Login |
Network | OkHttp, Retrofit2 |
Fragment Management | Navigation |
Strategy | Git Flow |
Other Tool | Notion, Slack |
Continuous Integration | Slack - Git auto notification |
๐ฆomoolen
โโom๐roid
โโ๐detail
โ โโ๐detailApi
โ โโ๐popular
โ โโ๐recommend
โโ๐home
โ โโ๐fragments
โ โ โโ๐five
โ โ โโ๐four
โ โ โโ๐one
โ โ โ โโ๐curating
โ โ โ โโ๐event
โ โ โ โโ๐networkApi
โ โ โ โโ๐newItem
โ โ โ โโ๐recommend
โ โ โ โโ๐tip
โ โ โโ๐three
โ โ โโ๐two
โ โ โโ๐api
โ โ โโ๐foryou
โ โ โโ๐newItem
โ โ โโ๐season
โ โ โโ๐situation
โ โโ๐homeApi
โโ๐login_signup
โ โโ๐login
โ โโ๐loginApi
โโ๐onboarding
โ โโ๐api
โ โโ๐fragments
โ โโ๐four
โ โ โโ๐brand
โ โ โโ๐when
โ โโ๐one
โ โ โโ๐recycle
โ โ โโ๐age
โ โ โโ๐gender
โ โโ๐three
โ โ โโ๐recycle
โ โ โโ๐effect
โ โ โโ๐period
โ โโ๐two
โ โโ๐recycle
โ โโ๐color
โ โโ๐what
โโ๐search
โ โโ๐data
โ โโ๐fragment
โ โ โโ๐one
โ โ โ โโ๐recycle
โ โ โ โโ๐popular
โ โ โ โโ๐recent
โ โ โโ๐two
โ โ โโ๐filterSearchApi
โ โ โโ๐recycle
โ โ โโ๐brand
โ โ โโ๐color
โ โ โโ๐diameter
โ โ โโ๐period
โ โโ๐search_result
โโ๐splash
โโ๐util
โโ๐api
โโ๐firebase
์ ์ง์ | ์ด์ ์ | ์ฐจ์ง์ |
์คํ๋์/์นด์นด์คํก ๋ก๊ทธ์ธ, ์จ๋ณด๋ฉ, ๊ฒ์(ํค์๋,ํํฐ) | ํ, ๋ฐ๊ฒฌ | ์ํ ์์ธ, ๊ฒ์ ์์ธ |