-
SignInActivity
-
'๋ก๊ทธ์ธ ๋ฒํผ' ํด๋ฆญ ์ ๋ชจ๋ EditText๊ฐ ์ ๋ ฅ๋์ด ์๋ ์ง ํ์ธ
if(!binding.etId.text.toString().isEmpty() && !binding.etPw.text.toString().isEmpty()) { //... }
-
๋น๋ฐ๋ฒํธ EditText inputType ์์ฑ
android:inputType="textPassword"
-
๋ชจ๋ ์ ๋ ฅ์ด ๋์์ ๋ ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ ์ HomeActivity๋ก ์ด๋
val intent = Intent(this, HomeActivity::class.java) startActivity(intent)
-
ํ์๊ฐ์ ๋ฒํผ ํด๋ฆญ ์ SignUpActivity๋ก ์ด๋
val intent = Intent(this, SignUpActivity::class.java) activityResultLauncher.launch(intent)
-
-
SignUpActivity
-
'ํ์๊ฐ์ ์๋ฃ' ๋ฒํผ ํด๋ฆญ ์ ๋ชจ๋ EditText๊ฐ ์ ๋ ฅ๋์ด ์๋ ์ง ํ์ธ
if(!etName.text.toString().isEmpty() && !etId.text.toString().isEmpty() && !etPw.text.toString().isEmpty()) { //... }
-
๋น๋ฐ๋ฒํธ EditText inputType ์์ฑ
android:inputType="textPassword"
-
-
ํ๋ฉด ์ด๋
-
SignUpActivity
binding.apply { btnSignUp.setOnClickListener { if(!etName.text.toString().isEmpty() && !etId.text.toString().isEmpty() && !etPw.text.toString().isEmpty()) { intent.putExtra("id", etId.text.toString()) intent.putExtra("pw", etPw.text.toString()) setResult(RESULT_OK, intent) finish() } else { Toast.makeText(this@SignUpActivity, "์ ๋ ฅ๋์ง ์์ ์ ๋ณด๊ฐ ์์ต๋๋ค", Toast.LENGTH_SHORT).show() } } }
-
SignInActivity
class SignInActivity : AppCompatActivity() { private lateinit var binding : ActivitySignInBinding private lateinit var activityResultLauncher : ActivityResultLauncher<Intent> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySignInBinding.inflate(layoutInflater) setContentView(binding.root) activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if(it.resultCode == RESULT_OK) { binding.etId.setText(it.data?.getStringExtra("id")) binding.etPw.setText(it.data?.getStringExtra("pw")) } } //... binding.btnSignUp.setOnClickListener { val intent = Intent(this, SignUpActivity::class.java) activityResultLauncher.launch(intent) } } }
-
-
์ธํ ํธ
-
๋ช ์์ ์ธํ ํธ
- ์ธํ ํธ์ ํด๋์ค ๊ฐ์ฒด๋ ์ปดํฌ๋ํธ ์ด๋ฆ์ ์ง์ ํ์ฌ ํธ์ถํ ๋์์ ํ์คํ ์ ์ ์๋ ๊ฒฝ์ฐ
- ์ฃผ๋ก ์ฑ ๋ด๋ถ์์ ์ฌ์ฉ
- ํน์ ์ปดํฌ๋ํธ๋ ์กํฐ๋นํฐ๊ฐ ๋ช ํํ๊ฒ ์คํ๋์ด์ผํ ๊ฒฝ์ฐ
-
์์์ ์ธํ ํธ
- ์ธํ ํธ์ ์ก์ ๊ณผ ๋ฐ์ดํฐ๋ฅผ ์ง์ ํ๊ธด ํ์ง๋ง, ํธ์ถํ ๋์์ด ๋ฌ๋ผ์ง ์ ์๋ ๊ฒฝ์ฐ
- ์๋๋ก์ด๋ ์์คํ ์ด ์ธํ ํธ๋ฅผ ์ด์ฉํด ์์ฒญํ ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ์ ์ ํ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ ์ฌ์ฉ์์๊ฒ ๊ทธ ๋์๊ณผ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค
- ํด๋น ๊ธฐ๋ฅ๋ค์ ์ง์ํ๋ ์ฑ๋ค์ด ์๋ ๊ฒฝ์ฐ์ ์์์ ์ธํ ํธ๋ฅผ ์ฌ์ฉํด์ ๊ทธ ์ฑ๋ค์ ์ฌ์ฉ
-
-
HomeActivity ํ๋ฉด ๋ ์ด์์ ์์
-
nestedScrollView ์ฌ์ฉ
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> //... </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView>
-
constraintDimensionRatio ์์ฑ ์ฌ์ฉ
- height๋ฅผ 0dp๋ก ์ค์ ํ๊ณ layout_constraintDimensionRatio์ 1์ ๋ฃ์ด์ width์ height๋ฅผ 1:1๋น์จ๋ก ์กฐ์
<ImageView android:id="@+id/iv_profile" android:layout_width="200dp" android:layout_height="0dp" //... app:layout_constraintDimensionRatio="1" />
-
-
ViewBinding & DataBinding
-
๊ณตํต์
- findViewById์ ๋นํด ์๋์ ์ผ๋ก ๊ฐ๋จํ๋ฉฐ ํผํฌ๋จผ์ค ํจ์จ์ด ์ข๊ณ ์ฉ๋ ์ ์ฝ ๊ฐ๋ฅ
- ๋ทฐ์ ์ง์ ์ฐธ์กฐ๋ฅผ ์์ฑํ๋ฏ๋ก ์ ํจํ์ง ์์ ๋ทฐ ID๋ก ์ธํ NPE๋ก๋ถํฐ ์์
-
ViewBinding์ ์ฅ์
- ๋น ๋ฅธ ์ปดํ์ผ ์๋์ ๋ฐ๋ก xml ํ์ผ์ ํ๊ทธ๊ฐ ํ์ํ์ง ์๊ณ ์๋์ผ๋ก ์ ์ฉ๋๋ฏ๋ก ์ฌ์ฉ ํธ๋ฆฌ
-
DataBinding์ ์ฅ์
- ๋ฐ์ดํฐ์ ๋ทฐ๋ฅผ ์ฐ๊ฒฐํ๋ ์์ ์ ๋ ์ด์์์์ ์ฒ๋ฆฌ ๊ฐ๋ฅ
- ๋์ UI ์ฝํ ์ธ ์ ์ธ ๋ฐ ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ ์ง์
-
์ฝํ๋ฆฐ์์ setOnClickListener๋ฅผ ๋๋ค์์ผ๋ก ๊ฐ๊ฒฐํ๊ฒ ํํํ ์ ์๋ ์ด์
- ์ฝํ๋ฆฐ์ด ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ
ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์๋ ํจ์๋ฅผ ๊ฐ์ฒ๋ผ ๋ค๋ฃจ๋ ์ ๊ทผ ๋ฐฉ์์ ํํจ์ผ๋ก์จ, ๊ธฐ์กด์ฒ๋ผ ํด๋์ค๋ฅผ ์ ์ธํ๊ณ ๊ทธ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ํจ์์ ๋๊ธฐ๋ ๋์ , ํจ์๋ฅผ ์ง์ ๋ค๋ฅธ ํจ์์ ์ ๋ฌํ ์ ์์.
์๋ฐ
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //TODO } });
์ฝํ๋ฆฐ
button.setOnClickListener { //TODO }
-
HomeActivity.kt
-
๊ฐ Fragment๋ก ์ด๋ํ๋ Button๊ณผ FragmentContainerView ์ถ๊ฐ
<Button android:id="@+id/btn_follower_list" ... /> <Button android:id="@+id/btn_repository_list" ... /> <androidx.fragment.app.FragmentContainerView android:id="@+id/fc_home_list" ... />
-
๊ฐ ๋ฒํผ์ ํด๋ฆญ ์ Fragment ์ด๋
private fun initBtn() { binding.btnGit.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/jaehoon-jo")) startActivity(intent) } binding.btnFollowerList.setOnClickListener { transFragment(FOLLOWER_BTN) } binding.btnRepositoryList.setOnClickListener { transFragment(REPOSITORY_BTN) } } private fun transFragment(btn : Int) { val transaction = supportFragmentManager.beginTransaction() when(btn) { FOLLOWER_BTN -> { val followerFragment = FollowerFragment() transaction.replace(R.id.fc_home_list, followerFragment).commit() } REPOSITORY_BTN -> { val repositoryFragment = RepositoryFragment() transaction.replace(R.id.fc_home_list, repositoryFragment).commit() } } }
-
-
FollowerFragment, FollowerAdapter, Follower ์์ฑ
-
FollowerFragment์ ๋ฆฌ์ฌ์ดํด๋ฌ๋ทฐ ์์ฑ
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_follower" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" ... />
-
-
RepositoryFragment, RepositoryAdapter, Repository ์์ฑ
-
RepositoryFragment์ ๋ฆฌ์ฌ์ดํด๋ฌ๋ทฐ ์์ฑ, GridLayoutManager
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_repository" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" ... />
-
-
SignInActivity.kt & SignUpActivity.kt
-
selector๋ฅผ ์ฌ์ฉํ์ฌ EditText๊ฐ focus ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋์์ธ ์ถ๋ ฅ
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:drawable="@drawable/edit_text_selected"/> <item android:state_focused="false" android:drawable="@drawable/edit_text_unselected"/> </selector>
-
๋ก๊ทธ์ธ, ํ์๊ฐ์ ๋ฒํผ์ shape๋ฅผ ํตํด round ์์ฑ ์ถ๊ฐ
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" android:tint="@color/sopt_pink2"> <corners android:radius="5dp"/> </shape>
-
-
ProfileFragment.kt
-
Button์ selector ํ์ฉ
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:drawable="@drawable/shape_profile_btn_selected" /> <item android:state_checked="false" android:drawable="@drawable/shape_profile_btn_unselected" /> </selector>
-
์ด๋ฏธ์ง Glide์ CircleCrop๊ธฐ๋ฅ ํ์ฉ
Glide.with(this) .load("https://www.riotgames.com/darkroom/2880/656220f9ab667529111a78aae0e6ab9f:d1a7c6d0384f2edf9672d9369a8e9083/01-logo.png") .circleCrop() .into(binding.ivProfile)
-
-
HomeFragment.kt
-
TabLayout + ViewPager2 ์ถ๊ฐ
<com.google.android.material.tabs.TabLayout android:id="@+id/tl_home_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabIndicatorHeight="3dp" app:tabIndicatorColor="@color/sopt_pink2" app:layout_constraintBottom_toTopOf="@+id/vp_home_fragment" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/vp_home_fragment" android:layout_width="match_parent" android:layout_height="338dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" />
-
-
POSTMAN ํ ์คํธ - ํ์๊ฐ์ ์๋ฃ & ๋ก๊ทธ์ธ ์๋ฃ
-
retrofit
-
retrofit interface
- LoginService.kt
interface LoginService { @Headers("Content-Type:application/json") @POST("user/login") fun postLogin( @Body body: RequestLoginData ) : Call<ResponseLoginData> @Headers("Content-Type:application/json") @POST("user/signup") fun postSignUp( @Body body: RequestSignUpData ) : Call<ResponseSignUpData> }
- LoginService.kt
-
retrofit ๊ตฌํ์ฒด
- ServiceCreator.kt
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 loginService : LoginService = retrofit.create(LoginService::class.java) val signUpService : LoginService = retrofit.create(LoginService::class.java) }
- ServiceCreator.kt
-
Request/Response ๊ฐ์ฒด
-
SignInActivity.kt
private fun initNetwork(){ val requestLoginData = RequestLoginData( email = binding.etId.text.toString(), password = binding.etPw.text.toString() ) val call: Call<ResponseLoginData> = ServiceCreator.loginService.postLogin(requestLoginData) call.enqueue(object : Callback<ResponseLoginData> { override fun onResponse( call: Call<ResponseLoginData>, response: Response<ResponseLoginData> ) { if(response.isSuccessful){ val data=response.body()?.data Toast.makeText(this@SignInActivity,"${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.kt
private fun initNetwork(){ val requestSignUpData = RequestSignUpData( email = binding.etId.text.toString(), name = binding.etName.text.toString(), password=binding.etPw.text.toString() ) val call: Call<ResponseSignUpData> = ServiceCreator.signUpService.postSignUp(requestSignUpData) call.enqueue(object : Callback<ResponseSignUpData> { override fun onResponse( call: Call<ResponseSignUpData>, response: Response<ResponseSignUpData> ) { if(response.isSuccessful){ val data=response.body()?.data Toast.makeText(this@SignUpActivity,"${data?.name}๋ ํ์๊ฐ์ ์๋ฃ", Toast.LENGTH_SHORT).show() val intent = Intent(this@SignUpActivity, SignInActivity::class.java) intent .putExtra("id", binding.etId.text.toString()) .putExtra("pw", binding.etPw.text.toString()) setResult(RESULT_OK, intent) finish() }else{ Toast.makeText(this@SignUpActivity,"ํ์๊ฐ์ ์คํจ", Toast.LENGTH_SHORT).show() } } override fun onFailure(call: Call<ResponseSignUpData>, t: Throwable) { Log.e("NetworkTest","error:$t") } }) }
-
-