Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jetpack navigation 및 바텀시트 프래그먼트 추가 #12 #15

Merged
merged 15 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions android/Staccato_AN/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ android {
)
}
}
dataBinding {
enable = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
Expand Down Expand Up @@ -87,4 +90,8 @@ dependencies {
// Mockk
testImplementation(libs.mockk.android)
testImplementation(libs.mockk.agent)

// Navigation
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
Comment on lines +96 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위의 다른 의존성 코드처럼 주석으로 구분해주신 것 너무 좋네요! 👍

}
33 changes: 19 additions & 14 deletions android/Staccato_AN/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- 카메라(추후 기능 추가) -->
<!-- 카메라(추후 기능 추가) -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<!-- 인터넷 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 위치 -->
android:required="false" /> <!-- 인터넷 -->
<uses-permission android:name="android.permission.INTERNET" /> <!-- 위치 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 앨범 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 앨범 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 카메라(추후 기능 추가) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 카메라(추후 기능 추가) -->
<uses-permission android:name="android.permission.CAMERA" />

<application
Expand All @@ -30,7 +23,19 @@
android:theme="@style/Theme.Staccato_AN"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".presentation.visitupdate.VisitUpdateActivity"
android:exported="false" />
<activity
android:name=".presentation.travelupdate.TravelUpdateActivity"
android:exported="false" />
<activity
android:name=".presentation.visitcreation.VisitCreationActivity"
android:exported="false" />
<activity
android:name=".presentation.travelcreation.TravelCreationActivity"
android:exported="false" />
<activity
android:name=".presentation.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -40,4 +45,4 @@
</activity>
</application>

</manifest>
</manifest>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.woowacourse.staccato.presentation.base

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding

abstract class BindingActivity<T : ViewDataBinding> : AppCompatActivity() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빙티 덕에 BindingActivity에 대해 처음 알았는 데 앞으로 너무 유용하게 잘 활용할 수 있을 것 같네요! 감사합니다 🫶

abstract val layoutResourceId: Int
private var _binding: T? = null
val binding
get() = requireNotNull(_binding)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DataBindingUtil.setContentView(this, layoutResourceId)
initStartView(savedInstanceState)
}

abstract fun initStartView(savedInstanceState: Bundle?)

override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.woowacourse.staccato.presentation.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment

abstract class BindingFragment<T : ViewDataBinding>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindingActivity와 함께 BindingFragment도 잘 만들어주신 것 같아요!
덕분에 개발할 때 아주 편리하게 사용할 것 같습니다~ 😄

@LayoutRes private val layoutRes: Int,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LayoutRes 라는 어노테이션으로 Layout의 ResourceId 만을 받도록 설정할 수 있군요!

) : Fragment() {
private var _binding: T? = null
protected val binding
get() = requireNotNull(_binding)

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.woowacourse.staccato.presentation.main

import android.os.Bundle
import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.navigation.NavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.woowacourse.staccato.R
import com.woowacourse.staccato.databinding.ActivityMainBinding
import com.woowacourse.staccato.presentation.base.BindingActivity
import com.woowacourse.staccato.presentation.travelcreation.TravelCreationActivity
import com.woowacourse.staccato.presentation.visitcreation.VisitCreationActivity

class MainActivity : BindingActivity<ActivityMainBinding>() {
override val layoutResourceId: Int
get() = R.layout.activity_main

private lateinit var behavior: BottomSheetBehavior<ConstraintLayout>
private lateinit var navHostFragment: NavHostFragment
private lateinit var navController: NavController

private val travelCreationLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.let {
Toast.makeText(this, "새로운 여행을 만들었어요!", Toast.LENGTH_SHORT).show()
navigateTo(R.id.travelFragment, R.id.timelineFragment)
}
}
}
Comment on lines +28 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityResult를 등록하여 런처를 만드는 코드를 아래처럼 메서드로 만들어볼 수 있을 것 같아요!

  fun createNavigationLauncher(navigteToId: Int, popToId: Int, toastMessage: String) {
    return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == RESULT_OK) {
                result.data?.let {
                    Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
                    navigateTo(navigateToId, popToId)
                }
             }
     }
  }


val travelUpdateLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

액티비티를 실행시키는 중복 코드들을 줄여볼 수 있을 것 같아요!

if (result.resultCode == RESULT_OK) {
result.data?.let {
Toast.makeText(this, "여행을 수정했어요!", Toast.LENGTH_SHORT).show()
navigateTo(R.id.travelFragment, R.id.timelineFragment)
}
}
}

private val visitCreationLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.let {
Toast.makeText(this, "새로운 방문 기록을 만들었어요!", Toast.LENGTH_SHORT).show()
navigateTo(R.id.visitFragment, R.id.visitFragment)
}
}
}

val visitUpdateLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.let {
Toast.makeText(this, "방문 기록을 수정했어요!", Toast.LENGTH_SHORT).show()
navigateTo(R.id.visitFragment, R.id.visitFragment)
}
}
}

override fun initStartView(savedInstanceState: Bundle?) {
setupBottomSheetController()
setupBottomSheetNavigation()
setupBackPressedHandler()
}

private fun setupBackPressedHandler() {
var backPressedTime = 0L
onBackPressedDispatcher.addCallback {
if (behavior.state == STATE_EXPANDED) {
behavior.state = STATE_COLLAPSED
} else {
handleBackPressedTwice(backPressedTime).also {
backPressedTime = it
}
}
}
}

private fun handleBackPressedTwice(backPressedTime: Long): Long {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앱 종료 전 뒤로가기를 2번 눌러야 종료되도록 처리해주신 점 너무 좋습니다 👍

val currentTime = System.currentTimeMillis()
if (currentTime - backPressedTime >= 3000L) {
Toast.makeText(this@MainActivity, "버튼을 한 번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT)
.show()
} else {
finish()
}
return currentTime
}

private fun setupBottomSheetController() {
behavior = BottomSheetBehavior.from(binding.constraintBottomSheet)
navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navController = navHostFragment.navController
}

private fun setupBottomSheetNavigation() {
binding.btnTravelCreate.setOnClickListener {
TravelCreationActivity.startWithResultLauncher(
this,
travelCreationLauncher,
)
}
binding.btnVisitCreate.setOnClickListener {
VisitCreationActivity.startWithResultLauncher(
this,
visitCreationLauncher,
)
}
binding.btnTimeline.setOnClickListener {
navigateTo(R.id.timelineFragment, R.id.timelineFragment)
}
}

private fun navigateTo(
navigateToId: Int,
popUpToId: Int,
) {
val navOptions = buildNavOptions(popUpToId)
navController.navigate(navigateToId, null, navOptions)
behavior.state = STATE_EXPANDED
}

private fun buildNavOptions(popUpToId: Int) =
NavOptions.Builder()
.setLaunchSingleTop(true)
.setPopUpTo(popUpToId, false)
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.woowacourse.staccato.presentation.timeline

import android.os.Bundle
import android.view.View
import androidx.navigation.fragment.findNavController
import com.woowacourse.staccato.R
import com.woowacourse.staccato.databinding.FragmentTimelineBinding
import com.woowacourse.staccato.presentation.base.BindingFragment

class TimelineFragment : BindingFragment<FragmentTimelineBinding>(R.layout.fragment_timeline) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
binding.btnTimeline.setOnClickListener {
findNavController().navigate(R.id.action_timelineFragment_to_travelFragment)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.woowacourse.staccato.presentation.travel

import android.os.Bundle
import android.view.View
import androidx.navigation.fragment.findNavController
import com.woowacourse.staccato.R
import com.woowacourse.staccato.databinding.FragmentTravelBinding
import com.woowacourse.staccato.presentation.base.BindingFragment
import com.woowacourse.staccato.presentation.main.MainActivity
import com.woowacourse.staccato.presentation.travelupdate.TravelUpdateActivity

class TravelFragment : BindingFragment<FragmentTravelBinding>(R.layout.fragment_travel) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
binding.btnTravelUpdate.setOnClickListener {
val travelUpdateLauncher = (activity as MainActivity).travelUpdateLauncher
TravelUpdateActivity.startWithResultLauncher(
this.requireActivity(),
travelUpdateLauncher,
)
// findNavController().navigate(R.id.action_travelFragment_to_travelUpdateFragment)
}
binding.btnVisit.setOnClickListener {
findNavController().navigate(R.id.action_travelFragment_to_visitFragment)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.woowacourse.staccato.presentation.travelcreation

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import com.woowacourse.staccato.R
import com.woowacourse.staccato.databinding.ActivityTravelCreationBinding
import com.woowacourse.staccato.presentation.base.BindingActivity

class TravelCreationActivity : BindingActivity<ActivityTravelCreationBinding>() {
override val layoutResourceId = R.layout.activity_travel_creation

override fun initStartView(savedInstanceState: Bundle?) {
binding.btnTravelCreateDone.setOnClickListener {
val resultIntent = Intent()
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}

companion object {
fun startWithResultLauncher(
context: Context,
activityLauncher: ActivityResultLauncher<Intent>,
) {
Intent(context, TravelCreationActivity::class.java).apply {
// putExtra(EXTRA_TRAVEL_ID, travelId)
activityLauncher.launch(this)
}
}
}
}
Loading
Loading