Skip to content

Latest commit

ย 

History

History
1207 lines (925 loc) ยท 35.3 KB

README.md

File metadata and controls

1207 lines (925 loc) ยท 35.3 KB

Android-Hyebin

github_์ดํ˜œ๋นˆ_ver1-25




1์ฃผ์ฐจ

1๏ธโƒฃ First Week

SignIn Login Home SignUp
ezgif com-gif-maker ezgif com-gif-maker (1) ezgif com-gif-maker (2) ezgif com-gif-maker (3)




1. SignUp

  • ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ์ด ๋ชจ๋‘ ๋˜์—ˆ์„ ๋•Œ๋งŒ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ๋ˆŒ๋ €์„ ๋•Œ HomeActivity๋กœ ์ด๋™ (ํ† ์ŠคํŠธ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ)
val intentHome = Intent(this, HomeActivity::class.java)

binding.apply {
     btnLogin.setOnClickListener {
         val userId : String = etId.text.toString()
         val userPw : String = etPassword.text.toString()
         if (userId.isNotEmpty() && userPw.isNotEmpty()) {
             startActivity(intentHome)
             Toast.makeText(this@SignInActivity, "$userId ๋‹˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค", Toast.LENGTH_SHORT).show()
         } else {
              Toast.makeText(this@SignInActivity, "๋กœ๊ทธ์ธ ์‹คํŒจ", Toast.LENGTH_SHORT).show()
         }
     }
 }



  • ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ๋ˆŒ๋ €์„ ๋•Œ ํ™”๋ฉด ์ด๋™
val intentSingUp = Intent(this, SignUpActivity::class.java)
        
btnSignup.setOnClickListener {
    startActivity(intentSingUp)
    finish()
}



  • ํšŒ์›๊ฐ€์ž…์— ์„ฑ๊ณตํ•œ ๋’ค, ์•„์ด๋””&ํŒจ์Šค์›Œ๋“œ ์ž๋™ ์ž…๋ ฅ
if (intent.hasExtra("id") && intent.hasExtra("pw")) {
    val id = intent.getStringExtra("id")
    val pw = intent.getStringExtra("pw")

    etId.setText(id)
    etPassword.setText(pw)
}



  • EditText์˜ hint ์†์„ฑ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ์˜ inputType ์†์„ฑ
<EditText
    android:id="@+id/et_password"
    android:layout_width="0dp"
    android:layout_height="50dp"
    android:layout_marginStart="40dp"
    android:layout_marginTop="10dp"
    android:layout_marginEnd="40dp"
    android:background="@drawable/et_border_pink"
    android:ems="10"
    android:hint="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
    android:textSize="15dp"
    android:paddingStart="20dp"
    android:inputType="textPassword"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tv_password" />




2. Home

  • Home ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ๋‚˜์˜ git ํŽ˜์ด์ง€๋กœ ์ด๋™ (์•”์‹œ์  ์ธํ…ํŠธ)
binding.btnGit.setOnClickListener{
    var intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/lhb8106"))
    startActivity(intent)
}

โœ…๋ช…์‹œ์  ์ธํ…ํŠธ์™€ ์•”์‹œ์  ์ธํ…ํŠธ์˜ ์ฐจ์ด์ โœ…

๋ช…์‹œ์  ์ธํ…ํŠธ๋Š” ์‹คํ–‰ํ•˜๊ณ ์žํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ช…ํ™•ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ฆ‰, ํŒจํ‚ค์ง€ ๋‚ด๋ถ€์˜ ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
์•”์‹œ์  ์ธํ…ํŠธ๋Š” ์–ด๋– ํ•œ ์ธํ…ํŠธ๋ฅผ ๋‹ด์•„์„œ ๋ณด๋‚ด๋ฉด, ์‹œ์Šคํ…œ์ด ์ ์ ˆํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์•„์„œ ์‹คํ–‰ํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.



  • ์‚ฌ์ง„ ๋น„์œจ ๋งž์ถ”๊ธฐ
 <ImageView
     android:id="@+id/iv_profile"
     android:layout_width="180dp"
     android:layout_height="0dp"
     android:layout_marginTop="20dp"
     android:src="@drawable/selca"
     app:layout_constraintDimensionRatio="1:1"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent" />



  • ์Šคํฌ๋กค๋ทฐ ์ ์šฉ
<ScrollView
    android:id="@+id/scroll"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginTop="20dp"
    android:layout_marginBottom="20dp"
    android:fillViewport="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tv_title">
</ScrollView>




3. SignIn

  • ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ ๋ฒ„ํŠผ ๋ˆŒ๋ €์„ ๋•Œ, ๋นˆ์นธ ํ™•์ธ ๋ฐ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ’ ๋„˜๊ฒจ์ฃผ๊ธฐ
val intent = Intent(this, SignInActivity::class.java)

binding.apply {
    btnSingup.setOnClickListener {
        val userName : String = etName.text.toString()
        val userId : String = etId.text.toString()
        val userPw : String = etPassword.text.toString()

         if (userName.isNotEmpty() && userId.isNotEmpty() && userPw.trim().isNotEmpty()) {
                    intent.putExtra("id", userId)
                    intent.putExtra("pw", userPw)
                    startActivity(intent)
                    finish()

         } else {
             Toast.makeText(this@SignUpActivity, "์ž…๋ ฅ๋˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค", Toast.LENGTH_SHORT).show()
         }
     }
  }



  • hint ์†์„ฑ ๋ฐ inputType ์†์„ฑ์€ ์œ„์˜ SignUp๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.







๐Ÿค์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๋ฐฐ์šด ๋‚ด์šฉ & ์„ฑ์žฅํ•œ ๋‚ด์šฉ๐Ÿค


**โ˜constraintlayout์„ ์ •ํ™•ํžˆ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค!**
์ง€๊ธˆ๊นŒ์ง€ ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์—์„œ LinearLayout์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ConstraintLayout์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์—†์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ ๊ณผ์ œ์™€ ์„ธ๋ฏธ๋‚˜๋ฅผ ํ†ตํ•ด ConstraintLayout์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ ์™ธ์—๋„ layout ๋‚ด์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ์†์„ฑ์„ ์ตํž ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. (inputType ์†์„ฑ & constraintDimensionRatio ์†์„ฑ)

โœŒintent๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
์ฒ˜์Œ์—๋Š” ์ž˜ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ฒˆ ํ™”๋ฉด์„ ์ด๋™ํ•˜๋Š” ์—ฐ์Šต์„ ํ•˜๋‹ค๋ณด๋‹ˆ, intent๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ํ™œ์šฉ ๋ฐฉ์•ˆ์— ๋Œ€ํ•ด์„œ ์ž˜ ์ตํž ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ hasExtra๋Š” ์•„์ง์€..์ž˜ ๋ชจ๋ฅด๊ฒ ๋„ค์š”.. ์–ผ๋ ๋šฑ๋‹น ์˜๋„๋Œ€๋กœ ์›€์ง์ด๊ธด ํ•˜์ง€๋งŒ ๋” ์ •ํ™•ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘Œgit๊ณผ notion
git์„ ๋‹ค๋ฃจ๋Š” ๋ฐฉ๋ฒ•์„ ํ•˜๋‚˜๋„ ๋ชจ๋ฅด๋Š” ์‚ฌ๋žŒ์ด์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์— ๊ณผ์ œ๋ฅผ ์ œ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์œ ํŠœ๋ธŒ๋„ ์ฐพ์•„๋ณด๊ณ  ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋ฌผ์–ด๋ณด๋ฉฐ ๊ณต๋ถ€๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค..
์•„์ง ๋ฐฐ์›Œ์•ผํ•  ์ ์ด ์‚ฐ๋”๋ฏธ์ด์ง€๋งŒ ์ฐจ๊ทผ์ฐจ๊ทผ ์˜ฌ๋ผ๊ฐ€์„œ ๋‚˜์ค‘์—๋Š” ๊ผญ ๊นƒ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค..โ—
๊ทธ๋ฆฌ๊ณ  notion์„ ์†ํŠธ ๋“ค์–ด์™€์„œ ์ฒ˜์Œ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค๋ณด๋‹ˆ, ์ž๋ฃŒ ํ•˜๋‚˜ ์ฐพ๋Š”๋ฐ์—๋„ ๊ฝค ๋งŽ์€ ์‹œ๊ฐ„์„ ์†Œ๋ชจํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ž์ฃผ ๋“ค์–ด๊ฐ€์„œ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋‹ค๋ณด๋‹ˆ notion์ด ์ ์  ์–ด๋ ต์ง€ ์•Š๊ฒŒ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค!
๋…ธ์…˜๊ณผ ๊นƒ.. ์ฐจ์ฐจ ์นœํ•ด์ง€๊ฒ ์Šต๋‹ˆ๋‹ค๐Ÿ’—







2์ฃผ์ฐจ

2๏ธโƒฃ Second Week

Home
ezgif com-gif-maker (5)



๐Ÿ™FollowerRecyclerView์™€ RepositoryRecyclerView์˜ ์ฝ”๋“œ๊ฐ€ ์œ ์‚ฌํ•˜๋ฏ€๋กœ FollowerRecyclerView ๊ตฌํ˜„์ฝ”๋“œ๋งŒ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ™


LEVEL1



1. FollowerRecyclerView

+) LinearLayoutManager ์‚ฌ์šฉ ( GridLayoutManage๊ด€๋ จ ์ฝ”๋“œ๋Š” ํ•˜๋‹จ์— ์ฒจ๋ถ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. )

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_follower"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        tools:itemCount="4"
        tools:listitem="@layout/item_follower_list" />



2. FolloweData

data class FollowerData(
    val name: String,
    val introduction: String
)



3. FollowrAdapter

class FollowerAdapter : RecyclerView.Adapter<FollowerAdapter.FollowerViewHolder>() {
    val userList = mutableListOf<FollowerData>()

    class FollowerViewHolder(private val binding : ItemFollowerListBinding) : RecyclerView.ViewHolder(binding.root){
        fun onBind(data: FollowerData) {
            binding.tvName.text = data.name
            binding.tvIntroduction.text = data.introduction
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowerViewHolder {
        val binding = ItemFollowerListBinding.inflate(LayoutInflater.from(parent.context),parent,false)

        return FollowerViewHolder(binding)
    }

    override fun onBindViewHolder(holder: FollowerViewHolder, position: Int) {
        holder.onBind(userList[position])
    }

    override fun getItemCount(): Int = userList.size
}



FollowerFragment

class FollowerFragment : Fragment() {
    private lateinit var follwerAdapter: FollowerAdapter
    private var _binding: FollowerFragmentBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FollowerFragmentBinding.inflate(layoutInflater, container,false)

        follwerAdapter = FollowerAdapter()
        binding.rvFollower.adapter = follwerAdapter

        follwerAdapter.userList.addAll(
            listOf(
                FollowerData("์ดํ˜œ๋นˆ1", "์•ˆ๋…•ํ•˜์„ธ์š”"),
                FollowerData("์ดํ˜œ๋นˆ2", "์•ˆ๋…•ํ•˜์„ธ์š”"),
                FollowerData("์ดํ˜œ๋นˆ3", "์•ˆ๋…•ํ•˜์„ธ์š”"),
                FollowerData("์ดํ˜œ๋นˆ4", "์•ˆ๋…•ํ•˜์„ธ์š”")
            )
        )
        follwerAdapter.notifyDataSetChanged()
        return binding.root

    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}



HomeActivity

class HomeActivity : AppCompatActivity() {
    private var postion = FIRST_POSITION
    private lateinit var binding : ActivityHomeBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityHomeBinding.inflate(layoutInflater)

        setContentView(binding.root)

        initTransactionEvent()
    }

    fun initTransactionEvent() {
        val followerFragment = FollowerFragment()
        val repositstoryFragment = RepositoryFragment()

        supportFragmentManager.beginTransaction().add(R.id.container_rv, followerFragment).commit()

        binding.btnFollower.setOnClickListener {
            supportFragmentManager.beginTransaction().replace(R.id.container_rv, followerFragment) .commit()
        }

        binding.btnRepository.setOnClickListener {
            supportFragmentManager.beginTransaction().replace(R.id.container_rv, repositstoryFragment) .commit()
        }
    }


    companion object {
        const val FIRST_POSITION = 1
    }

}



xml ellipsize ์†์„ฑ

<TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:ellipsize="end"
        android:maxLines="1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"
        tools:text="๋‚ด์šฉ" />



RepositoryRecyclerView ์†์„ฑ
+)GridlayoutManager ์‚ฌ์šฉ

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_repository"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
        app:spanCount="2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:itemCount="4"
        android:layout_marginLeft="55dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="10dp"
        tools:listitem="@layout/item_repository_list" />

LEVEL2-2


์‹œํ—˜๊ธฐ๊ฐ„์ด๋ผ.. 2-2๋งŒ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.. ๋‹ค๋ฅธ ๋ถ€๋ถ„์€ ์ฐจ์ฐจ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค...



Decoration

   class Decoration(val colorString: String, val left: Int, val right: Int, val height: Int, val bottom:Int) : RecyclerView.ItemDecoration() {
    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDraw(c, parent, state)

        val paint = Paint().apply {
            color = Color.parseColor(colorString)
        }

        for (i in 0 until parent.childCount) {
            val child = parent.getChildAt(i)
            if (i != parent.childCount - 1) {
                c.drawRect(child.left.toFloat(), child.bottom.toFloat(), child.right.toFloat(), child.bottom.toFloat() + height, paint)
            }
        }
    }
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        super.getItemOffsets(outRect, view, parent, state)
        outRect.right = right
        outRect.left = left
        outRect.top = height
        outRect.bottom = height
    }
}



FollowerFragment

binding.rvFollower.addItemDecoration(Decoration("#F658A6", 50,50,25,25))







๐Ÿค์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๋ฐฐ์šด ๋‚ด์šฉ & ์„ฑ์žฅํ•œ ๋‚ด์šฉ๐Ÿค


โ˜Fragment์— ๋Œ€ํ•ด ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค.
์‹ค์Šต ์˜ˆ์ œ๋งŒ ๋”ฐ๋ผํ•˜๊ณ  ์ง์ ‘ ์›ํ•˜๋Š” ์š”์†Œ๋ฅผ ์‚ด๋ ค์„œ ๊ตฌํ˜„์€ ํ•ด๋ณธ ์ ์ด ์—†์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ ๊ธฐํšŒ๋ฅผ ํ†ตํ•ด ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ์ œ๋Œ€๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  fragment์—์„œ binding์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ• ๋˜ํ•œ ์ตํž ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๋Š”๋ฐ ์ด๋ฒˆ ๊ธฐํšŒ๋ฅผ ํ†ตํ•ด ์ œ๋Œ€๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

โœŒItemDecoration์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์›๋ž˜ xml์—์„œ margin๊ฐ’์„ ๋ชจ๋‘ ์คฌ๋Š”๋ฐ, ์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ItemDecoration์„ ์ฒ˜์Œ ์•Œ๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ์ œ๊ฐ€๋ด๋„ ์•„์ง ๋ถ€์กฑํ•˜๊ณ .. ์ด์ƒํ•œ.. ์ฝ”๋“œ์ง€๋งŒ ๋” ์—ด์‹ฌํžˆ ๊ณต๋ถ€ํ•ด์„œ.. ๋‹ค์Œ์ฃผ์— ๋” ๋‚˜์€ ์ฝ”๋“œ๋กœ ๋ฐ”๊ฟ”์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค









3์ฃผ์ฐจ

3๏ธโƒฃ Third Week

SignUp SignIn Profile Home ViewPager
ezgif com-gif-maker (6) ezgif com-gif-maker (7) ezgif com-gif-maker (8) ezgif com-gif-maker (9) ezgif com-gif-maker (10)



LEVEL1



1-1. EditText์— selector ํ™œ์šฉํ•˜๊ธฐ

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/et_border_pink" android:state_focused="true"/>
    <item android:drawable="@drawable/et_fill_gray" android:state_focused="false"/>
</selector>



1-2. ๋ฒ„ํŠผ ๋“ฑ๋“ฑ Drawable๋กœ ์ง์ ‘ ๋งŒ๋“ค๊ธฐ

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/pink"/>
    <corners
        android:bottomRightRadius="5dp"
        android:bottomLeftRadius="5dp"
        android:topLeftRadius="5dp"
        android:topRightRadius="5dp"/>



โœButton์— selector ํ™œ์šฉํ•˜๊ธฐ๋Š” ์œ„์˜ EditText์— selector ํ™œ์šฉํ•˜๊ธฐ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค!


2-1. ์ด๋ฏธ์ง€ Glide์˜ CircleCrop ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด์„œ ๋„ฃ์–ด์ฃผ๊ธฐ

 Glide.with(this)
            .load("https://mblogthumb-phinf.pstatic.net/MjAxOTA0MjNfMjcy/MDAxNTU2MDIwNjg0ODMw.KwUiIDMhdpKzsuNX83GpdFljS1HjgNhCBNcXv2QXfxkg.ksHQVjDUTn8AMV4XVSfETLX-tZ1LTz9-bOmO0o7AtI8g.JPEG.ndh7782/%EC%B9%98%EC%A6%8801.JPG?type=w800")
            .apply(RequestOptions.circleCropTransform())
            .into(binding.ivProfile)



2-2. ์•„์ด์ฝ˜ ์ด๋ฏธ์ง€ exportํ•ด์„œ ์‚ฌ์šฉ

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu_profile"
        android:icon="@drawable/ic_person_gray"
        android:title="ํ”„๋กœํ•„" />

    <item
        android:id="@+id/menu_home"
        android:icon="@drawable/ic_home_gray"
        android:title="ํ™ˆ" />

    <item
        android:id="@+id/menu_camera"
        android:icon="@drawable/ic_camera_gray"
        android:title="์นด๋ฉ”๋ผ" />
</menu>



2-3. ํ•˜๋‹จ์— BottomNavigation ๋„ฃ์–ด์ฃผ๊ธฐ

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        app:menu="@menu/menu_bottom"
        app:itemIconTint="@drawable/selector_icon"
        app:itemTextColor="@drawable/selector_icon"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />



xml ellipsize ์†์„ฑ

<TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:ellipsize="end"
        android:maxLines="1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"
        tools:text="๋‚ด์šฉ" />



3-1 TabLayout + ViewPager2

 <com.google.android.material.tabs.TabLayout
        android:id="@+id/tl_follow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="200dp"
        android:fontFamily="@font/noto_sans_kr_regular"
        android:textFontWeight="500"
        android:textSize="16sp"
        app:tabIndicatorColor="@color/pink"
        app:tabIndicatorHeight="3dp"
        app:tabSelectedTextColor="@color/pink"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </com.google.android.material.tabs.TabLayout>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp_follow"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="13dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tl_follow" />

LEVEL2-2



FollowerAdapter

 Glide.with(itemView.context).load(data.photo)
                .apply(RequestOptions.circleCropTransform())
                .into(binding.ivProfile)



FollowerData

data class FollowerData(
    val name: String,
    val introduction: String,
    val photo : String
)



FollowerFragment

 follwerAdapter.userList.addAll(
            listOf(
                FollowerData("์Šคํฐ์ง€๋ฐฅ", "์•ˆ๋…•ํ•˜์„ธ์š”", "https://ww.namu.la/s/bd52223e4d1f11fcc4c7f6506bf3321b26579bf118db6c1ca20492b9af4228a414edd25f1006baace220e4ca771288e0f38d6cbf253ae4e9d39aaf4b881600b0d65e518e7d94891837ee9a0c6a723aac0f4d2b7bf4a65b36bd1fe636aa49c632"),
                FollowerData("๋šฑ์ด", "์•ˆ๋…•ํ•˜์„ธ์š”", "https://img.insight.co.kr/static/2020/08/12/700/fyzvinle3b068ce501hq.jpg"),
                FollowerData("์ง‘๊ฒŒ์‚ฌ์žฅ", "์•ˆ๋…•ํ•˜์„ธ์š”", "https://pbs.twimg.com/media/D8RITHlV4AAb1iG.jpg")
            )
        )







๐Ÿค์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๋ฐฐ์šด ๋‚ด์šฉ & ์„ฑ์žฅํ•œ ๋‚ด์šฉ๐Ÿค


โ˜๋””์ž์ธ์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ตํ˜”์Šต๋‹ˆ๋‹ค
์ดˆ๊ธฐ์— ์ง์ ‘ ๋ ˆ์ด์•„์›ƒ์„ ์งฐ์„ ๋•Œ์™€ ๋””์ž์ด๋„ˆ๋ถ„๊ป˜์„œ ๋””์ž์ธํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๋ณด๋ฉฐ ์ˆ˜์ •์„ ํ•ด๋‚˜๊ฐ€๋ฉด์„œ ๋‚˜์˜ค๋Š” ๊ฒฐ๊ณผ๋ฌผ์˜ ์ฐจ์ด๋ฅผ ๋ณด๊ณ  ๋””์ž์ด๋„ˆ์˜ ์ค‘์š”์„ฑ๊ณผ ํ˜‘์—…์˜ ์ค‘์š”์„ฑ์„ ๊นจ๋‹ฌ์„ ์ˆ˜ ์žˆ๋Š” ๊ณ„๊ฐ€๊ธฐ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, ํ”ผ๊ทธ๋งˆ๋ฅผ ์ œ๋Œ€๋กœ ๋‹ค๋ค„๋ณธ ์ ์ด ํ•œ๋ฒˆ๋„ ์—†์—ˆ๋Š”๋ฐ, ํ˜‘์—…์— ์žˆ์–ด์„œ ํ”ผ๊ทธ๋งˆ๋ฅผ ์“ฐ๋Š” ๋ฒ•์„ ์ตํž ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

โœŒViewPager2๋ฅผ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค
์–ดํ”Œ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๋งŽ์ด ๋ดค๋˜ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์˜€๋Š”๋ฐ, ์ด๋ฒˆ ์„ธ๋ฏธ๋‚˜๋ฅผ ํ†ตํ•ด ViewPager2๋ฅผ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, TabLayout๋“ฑ๋“ฑ๋„ ํ•จ๊ป˜ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด์„œ ๋œป๊นŠ์—ˆ์Šต๋‹ˆ๋‹ค.




4์ฃผ์ฐจ # 4๏ธโƒฃ Fourth Week
LogIn
ezgif com-gif-maker (11)



LEVEL1



PostMan ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ฒจ๋ถ€

POST GET
POST ์กฐํšŒ



RequestData

data class RequestLoginData(
    @SerializedName("email")
    val email: String,
    val password: String
)

data class RequestSignUpData(
    @SerializedName("email")
    val email: String,
    val name: String,
    val password: String
)



ResponseData

data class ResponseLoginData(
    val status: Int,
    val success: Boolean,
    val message: String,
    val data: Data
) {
    data class Data(
        val id: Int,
        val name: String,
        val email: String
    )
}


data class ResponseSignUpData(
    val status: Int,
    val success: Boolean,
    val message: String,
    val data: Data
) {
    data class Data(
        val id: Int,
        val name: String,
        val email: String
    )
}



SampleService

interface SampleService {
    @Headers("Content-Type: application/json")
    @POST("user/login")
    fun postLogin(
        @Body requestLoginData: RequestLoginData
    ) : Call<ResponseLoginData>
}

interface SignUpService {
    @Headers("Content-Type: application/json")
    @POST("user/signup")
    fun postSignUp(
        @Body requestSignUpData: RequestSignUpData
    ) : Call<ResponseSignUpData>
}



ServiceCreator

<menu xmlns:android="http://schemas.android.com/apk/res/android">
object ServiceCreator {
    private const val BASE_URL = "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"

    private val retrofit: Retrofit = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val sampleService : SampleService = retrofit.create(SampleService::class.java)
}


object SignUpCreator {
    private const val BASE_URL = "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"

    private val retrofit: Retrofit = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val signUpService : SignUpService = retrofit.create(SignUpService::class.java)
}



SignInActivity

 private fun initNetwork() {
        val requestLoginData = RequestLoginData(
            binding.etId.text.toString(),
            binding.etPassword.text.toString()
        )

        val call : Call<ResponseLoginData> = ServiceCreator.sampleService.postLogin(requestLoginData)
        call.enqueue(object : Callback<ResponseLoginData> {
            override fun onResponse(
                call: Call<ResponseLoginData>,
                response: Response<ResponseLoginData>
            ) {
                if(response.isSuccessful) {
                    Toast.makeText(this@SignInActivity, "${response.body()?.data?.name}๋‹˜ ๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค", Toast.LENGTH_SHORT).show()
                    startActivity(Intent(this@SignInActivity,HomeActivity::class.java))
                } else {
                    Toast.makeText(this@SignInActivity, "๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค", Toast.LENGTH_SHORT).show()
                }

            }

            override fun onFailure(call: Call<ResponseLoginData>, t: Throwable) {
                Log.e("NetworkTest", "error: $t")
            }

        })
    }



SignUpActivity

 private fun initNetwork() {
        val requestSignUpData = RequestSignUpData(
            binding.etName.text.toString(),
            binding.etId.text.toString(),
            binding.etPassword.text.toString()
        )

        val call : Call<ResponseSignUpData> = SignUpCreator.signUpService.postSignUp(requestSignUpData)
        call.enqueue(object : Callback<ResponseSignUpData> {
            override fun onResponse(
                call: Call<ResponseSignUpData>,
                response: Response<ResponseSignUpData>
            ) {
                if (response.isSuccessful) {
                    Toast.makeText(this@SignUpActivity, response.body()?.message, Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this@SignUpActivity, "ํšŒ์›๊ฐ€์ž… ์‹คํŒจ", Toast.LENGTH_SHORT).show()
                }
            }

            override fun onFailure(call: Call<ResponseSignUpData>, t: Throwable) {
                Log.e("NetworkTest", "error: $t")
            }
        })
    }







๐Ÿค์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๋ฐฐ์šด ๋‚ด์šฉ & ์„ฑ์žฅํ•œ ๋‚ด์šฉ๐Ÿค


โ˜์„œ๋ฒ„๋ฅผ ์ตํ˜”์Šต๋‹ˆ๋‹ค
์„œ๋ฒ„๊ฐ€ ๋ญ”์ง€ ์ •๋ง ํ•˜๋‚˜๋„ ๋ชจ๋ฅด๊ณ  ๋‹ค ์ฒ˜์Œ ๋ณด๋Š” ์ฝ”๋“œ์™€ ๊ฐœ๋… ํˆฌ์„ฑ์ด์–ด์„œ ์กฐ๊ธˆ์€ ์–ด๋ ต๊ฒŒ ๋Š๊ปด์กŒ์—ˆ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ฅ
๊ทธ๋ž˜๋„! ์‹ค์Šต๊ณผ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด... ์•„์ฃผ ์กฐ๊ธˆ์€.. ์ดํ•ด๋ฅผ ํ•œ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ญ๋‹ˆ๋‹ค!
๋” ๋ณต์Šตํ•˜๊ณ  ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค!




7์ฃผ์ฐจ

7๏ธโƒฃ Seventh Week

onBoarding autoLogin autoLogin cancel backstack
ezgif com-gif-maker (12) ezgif com-gif-maker (13) ezgif com-gif-maker (14) ezgif com-gif-maker (16)



LEVEL1



๐Ÿคview๋Š” notion์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค๐Ÿค


1-1. ์˜จ๋ณด๋”ฉ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ


nav_onboarding.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_onboarding"
    app:startDestination="@id/onboardingFragment1">

    <fragment
        android:id="@+id/onboardingFragment1"
        android:name="com.example.myapplication.view.onboarding.OnboardingFragment1"
        android:label="fragment_onboarding1"
        tools:layout="@layout/fragment_onboarding1" >
        <action
            android:id="@+id/action_onboardingFragment1_to_onboardingFragment2"
            app:destination="@id/onboardingFragment2" />
    </fragment>

    <fragment
        android:id="@+id/onboardingFragment2"
        android:name="com.example.myapplication.view.onboarding.OnboardingFragment2"
        android:label="OnboardingFragment2" >
        <action
            android:id="@+id/action_onboardingFragment2_to_onboardingFragment3"
            app:destination="@id/onboardingFragment3" />
    </fragment>

    <fragment
        android:id="@+id/onboardingFragment3"
        android:name="com.example.myapplication.view.onboarding.OnboardingFragment3"
        android:label="OnboardingFragment3">
        <action
            android:id="@+id/action_onboardingFragment3_to_signInActivity"
            app:destination="@id/signInActivity" />
    </fragment>
    <activity
        android:id="@+id/signInActivity"
        android:name="com.example.myapplication.view.login.SignInActivity"
        android:label="activity_main"
        tools:layout="@layout/activity_signin" />
</navigation>



OnboardingFragment1


๊ฐ ํ”„๋ ˆ๊ทธ๋จผํŠธ๋ณ„๋กœ buttonEvent๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค


fun initBtnEvent() {
        binding.btnNext.setOnClickListener {
            findNavController().navigate(R.id.action_onboardingFragment1_to_onboardingFragment2)
        }
    }



1-2. SharedPreferences ํ™œ์šฉํ•ด์„œ ์ž๋™๋กœ๊ทธ์ธ/์ž๋™๋กœ๊ทธ์ธ ํ•ด์ œ


activity_cancel_auto_login

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.profile.CancelAutoLoginActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:background="@color/pink"
        android:id="@+id/constraintLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/tv_setting"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="30dp"
            android:layout_marginBottom="30dp"
            android:fontFamily="@font/noto_sans_kr_regular"
            android:text="ํ™˜๊ฒฝ์„ค์ •"
            android:textColor="@color/white"
            android:textFontWeight="800"
            android:textSize="30sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>


    <TextView
        android:id="@+id/tv_off_auto_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:fontFamily="@font/noto_sans_kr_regular"
        android:text="์ž๋™๋กœ๊ทธ์ธ ํ•ด์ œ"
        android:textColor="@color/maingray"
        android:textFontWeight="400"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/constraintLayout" />

    <LinearLayout
        android:id="@+id/ll_line"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_marginTop="20dp"
        android:background="@color/lightgray"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_off_auto_login" />


</androidx.constraintlayout.widget.ConstraintLayout>



SiginInActivity

private fun initClickEvent() {
    binding.ivAutoLogin.setOnClickListener {
        binding.ivAutoLogin.isSelected = !binding.ivAutoLogin.isSelected
            SOPTSharedPreferences.setAutoLogin(this, binding.ivAutoLogin.isSelected)
        }
    }

private fun isAutoLogin() {
    if(SOPTSharedPreferences.getAutoLogin(this)) {
         shortToast("์ž๋™๋กœ๊ทธ์ธ ์™„๋ฃŒ")
         startActivity(Intent(this, HomeActivity::class.java))
         finish()
    }
}



CancelAutoLoginActivity

private fun initClickEvent() {
    binding.tvOffAutoLogin.setOnClickListener {
        val settings: SharedPreferences = getSharedPreferences("USER_AUTH", MODE_PRIVATE)
        val editor: SharedPreferences.Editor = settings.edit()
        editor.remove("int")
        editor.clear()
        editor.commit()
    }
}



1-3 ๋ณธ์ธ์ด ์‚ฌ์šฉํ•˜๋Š” Util ํด๋ž˜์Šค ์ฝ”๋“œ ๋ฐ ํŒจํ‚ค์ง• ๋ฐฉ์‹
util์—์„œ๋Š” ์„ธ๋ฏธ๋‚˜ ์‹œ๊ฐ„์— ๋‹ค๋ฃฌ shortToast๋ฅผ ์ •์˜ํ•ด๋‘” ์ƒํƒœ์ž…๋‹ˆ๋‹ค.
์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ๊ธฐ๋Šฅ์„ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค์–ด ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

fun Context.shortToast(message: String) {
  Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

- ํŒจํ‚ค์ง• ๋ฐฉ์‹
-์ž์ฃผ ์ƒ์„ฑ๋˜๋Š” ํด๋ž˜์Šค์ธ adapter, api, data, util, view๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํ•˜์—ฌ ํŒจํ‚ค์ง€๋ฅผ ๋‚˜๋ˆด์Šต๋‹ˆ๋‹ค.

โ”ฃ ๐Ÿ“‚adapter
โ”ฃ ๐Ÿ“‚api
โ”ฃ ๐Ÿ“‚data
โ”ฃ ๐Ÿ“‚util
โ”— ๐Ÿ“‚view

โ”ฃ ๐Ÿ“‚camera
โ”ฃ ๐Ÿ“‚home
โ”ฃ ๐Ÿ“‚login
โ”ฃ ๐Ÿ“‚onboarding
โ”— ๐Ÿ“‚profile

LEVEL2



2-1. NavigationComponent์—์„œ BackStack๊ด€๋ฆฌ

<fragment
      android:id="@+id/onboardingFragment2"
      android:name="com.example.myapplication.view.onboarding.OnboardingFragment2"
      android:label="OnboardingFragment2" >
      <action
          android:id="@+id/action_onboardingFragment2_to_onboardingFragment3"
          app:destination="@id/onboardingFragment3"
          app:popUpTo="@id/onboardingFragment2"
          app:popUpToInclusive="true"/>
  </fragment>







๐Ÿค์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๋ฐฐ์šด ๋‚ด์šฉ & ์„ฑ์žฅํ•œ ๋‚ด์šฉ๐Ÿค


โ˜navigation
๋ณดํ†ต ์–ดํ”Œ์„ ์‹คํ–‰ํ–ˆ์„๋•Œ ์ž์ฃผ ์ ‘ํ–ˆ๋˜ ์˜จ๋ณด๋”ฉ ํ™”๋ฉด์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๋ผ์„œ ๋œป๊นŠ๊ณ  ์•ž์œผ๋กœ ๋งŽ์ด ํ™œ์šฉ๋  ๊ฒƒ ๊ฐ™์•„ ๊ต‰์žฅํžˆ ๋งŽ์ด ๋ฐฐ์šด ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

โœŒutil์— ๋Œ€ํ•ด ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค
ํ•ญ์ƒ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ๊ผญ ์ €๋ ‡๊ฒŒ ์จ์•ผํ•˜๋‚˜๋ผ๋Š” ๊ฒƒ์„ ๋งŽ์ด ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ์ด๋ฒˆ ์„ธ๋ฏธ๋‹ˆ์™€ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด util์„ ๋ฐฐ์šฐ๊ณ , ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™” ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘Œpackage๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค
ํด๋ž˜์Šค๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์„œ ์ด์ œ ๋ญ๊ฐ€ ๋ญ”์ง€ ํ—ท๊ฐˆ๋ฆฌ๊ธฐ ์‹œ์ž‘ํ–ˆ๋Š”๋ฐ, ์ด๋•Œ ๋”ฑ ํŒจํ‚ค์ง•์— ๋Œ€ํ•ด์„œ ๋ฐฐ์šฐ๊ณ  ํ™œ์šฉํ•ด๋ณด๋Š” ๊ธฐํšŒ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์•ž์œผ๋กœ ์ œ๊ฐ€ ์ •์˜ํ•œ ํŒจํ‚ค์ง• ๋ฐฉ์‹์— ๋”ฐ๋ผ ์‚ฌ์šฉํ•˜๋ฉด ๋‚˜์ค‘์—๋„ ๋ณด๋‹ค ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.