-
Notifications
You must be signed in to change notification settings - Fork 33
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
부산대 Android_이지은 1주차 과제 #33
base: jieunyume
Are you sure you want to change the base?
Changes from all commits
60111c0
8b87436
e027741
db33a06
7a9e133
b749617
2672ebb
53b807a
4ef6a4f
9458e4e
1d4b3fa
d2891b8
fb40a87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,28 @@ | ||
# android-contacts | ||
## 기능 목록 | ||
### step1 | ||
- 연락처 정보 입력하기 | ||
- 이름: 필수-Toast 메세지(이름을 입력해주세요.) | ||
- 전화번호 | ||
- 필수-Toast 메세지(전화번호를 입력해주세요.) | ||
- 숫자만 가능-Toast 메세지(전화번호는 숫자로만 입력해주세요.) | ||
- 메일 | ||
- 더보기 | ||
- 클릭하면 입력 폼이 확장된다. | ||
- 생일: 캘린더 | ||
- 성별: 여자, 남자 중에 선택한다. | ||
- 메모 | ||
- 저장하기 | ||
- Toast(저장이 완료되었습니다.) | ||
- 취소하기 | ||
- Toast(취소되었습니다.) | ||
|
||
### step2 | ||
- 연락처 목록 | ||
- 이름 앞글자가 적힌 사진과 이름 정보가 나타난다. | ||
- 스크롤이 가능하다. | ||
- 연락처 추가 | ||
- 추가 도중 취소하면 확인 팝업이 나타난다. | ||
- 추가가 완료되면 Toast(저장이 완료되었습니다.) | ||
- 연락처 상세 보기 | ||
- 이름과 전화번호 정보가 나타난다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.app.DatePickerDialog | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.view.View | ||
import android.widget.Button | ||
import android.widget.EditText | ||
import android.widget.RadioGroup | ||
import android.widget.Toast | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.appcompat.widget.LinearLayoutCompat | ||
import java.util.Calendar | ||
|
||
class ContactAddActivity : AppCompatActivity() { | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
|
||
val name_input: EditText = findViewById(R.id.name_input) | ||
val phone_number_input: EditText = findViewById(R.id.phone_number_input) | ||
val email_input: EditText = findViewById(R.id.email_input) | ||
|
||
val birth_date_input: EditText = findViewById(R.id.birth_date_input) | ||
val birth_date_radiogroup: RadioGroup = findViewById(R.id.birth_date_radiogroup) | ||
|
||
val gender_input: EditText = findViewById(R.id.gender_input) | ||
val memo_input: EditText = findViewById(R.id.memo_input) | ||
|
||
val save_button: Button = findViewById(R.id.save_button) | ||
val cancel_button: Button = findViewById(R.id.cancel_button) | ||
val see_more_button: LinearLayoutCompat = findViewById(R.id.see_more_button) | ||
val see_more_input_form: LinearLayoutCompat = findViewById(R.id.see_more_input_form) | ||
|
||
see_more_input_form.visibility = View.GONE | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. View 의 extension 함수 중 isVisible 이라는 놈이 존재합니다. 이것을 사용한다면 아래처럼 조금 더 간편하게 visibility 를 컨트롤할 수 있을 것입니다. seeMoreInputForm.isVisible = false |
||
birth_date_input.setClickable(false) | ||
birth_date_input.setFocusable(false) | ||
gender_input.setClickable(false) | ||
gender_input.setFocusable(false) | ||
|
||
see_more_button.setOnClickListener{ | ||
see_more_button.visibility = View.GONE | ||
see_more_input_form.visibility = View.VISIBLE | ||
} | ||
|
||
birth_date_input. setOnClickListener{ | ||
var calendar = Calendar.getInstance() | ||
var year = calendar.get(Calendar.YEAR) | ||
var month = calendar.get(Calendar.MONTH) | ||
var day = calendar.get(Calendar.DAY_OF_MONTH) | ||
this.let { it1 -> | ||
DatePickerDialog(it1, { _, year, month, day -> | ||
run { | ||
birth_date_input.setText(year.toString() + "." + (month + 1).toString() + "." + day.toString()) | ||
} | ||
}, year, month, day) | ||
}?.show() | ||
Comment on lines
+51
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서의 let 은 사용하지 않는 것이 좋을 것 같아요. apply, let, run, also, with 등을 범위 지정 함수라고 하는데, 코틀린 코드에 익숙해지기 전까지는 사용하지 않는 것이 조금 더 성장에 도움이 될 것 같아요. 만약 사용하고 싶다면, 도움이 되는 레퍼런스 공유드려요. |
||
} | ||
|
||
birth_date_radiogroup.run { | ||
setOnCheckedChangeListener { group, checkedId -> | ||
when(checkedId){ | ||
R.id.woman_radiobutton -> {gender_input.setText("여성")} | ||
R.id.man_radiobutton -> {gender_input.setText("남성")} | ||
} | ||
} | ||
} | ||
|
||
save_button.setOnClickListener { | ||
if (name_input.text.isNullOrEmpty()) { | ||
Toast.makeText(this, "이름을 입력해주세요.", Toast.LENGTH_SHORT).show() | ||
} | ||
else if (phone_number_input.text.isNullOrEmpty()) { | ||
Toast.makeText(this, "전화번호를 입력해주세요.", Toast.LENGTH_SHORT).show() | ||
} else if (!phone_number_input.text.all { Character.isDigit(it) }){ | ||
Toast.makeText(this, "전화번호는 숫자로만 입력해주세요.", Toast.LENGTH_SHORT).show() | ||
} | ||
else{ | ||
Toast.makeText(this, "저장이 완료되었습니다.", Toast.LENGTH_SHORT).show() | ||
val contact: Contact = Contact( | ||
name_input.text.toString(), | ||
phone_number_input.text.toString(), | ||
email_input.text.toString(), | ||
birth_date_input.text.toString(), | ||
gender_input.text.toString(), | ||
memo_input.text.toString() | ||
) | ||
val intent: Intent = Intent() | ||
intent.putExtra("contactInfo", contact) | ||
setResult(RESULT_OK, intent) | ||
finish() | ||
} | ||
Comment on lines
+70
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 if 문이 반복해서 나올 경우에는 when 절으로 처리하는 것이 조금 더 명확합니다. when {
name_input.text.isNullOrEmpty() -> {
Toast.makeText(this, "이름을 입력해주세요.", Toast.LENGTH_SHORT).show()
}
phone_number_input.text.isNullOrEmpty() -> {
Toast.makeText(this, "전화번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
}
!phone_number_input.text.all { Character.isDigit(it) } -> {
Toast.makeText(this, "전화번호는 숫자로만 입력해주세요.", Toast.LENGTH_SHORT).show()
}
else -> {
// do else things...
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 또한 각 when 절에서 직접 수식값을 사용하기보다, 명확한 boolean 형태의 값을 지역변수로 선언하여 사용하면 리뷰어가 리뷰하기 조금 더 수월해집니다. 추후 코드 관리 측면에서도 이해하기 편하구요. val isNameEmpty = name_input.text.isNullOrEmpty()
val isPhoneNumberEmpty = phone_number_input.text.isNullOrEmpty()
val isPhoneNumberNotDigits = !phone_number_input.text.all { Character.isDigit(it) }
when {
isNameEmpty-> {
Toast.makeText(this, "이름을 입력해주세요.", Toast.LENGTH_SHORT).show()
}
isPhoneNumberEmpty-> {
Toast.makeText(this, "전화번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
}
isPhoneNumberNotDigits -> {
Toast.makeText(this, "전화번호는 숫자로만 입력해주세요.", Toast.LENGTH_SHORT).show()
}
else -> {
// do else things...
}
} |
||
} | ||
cancel_button.setOnClickListener { | ||
Toast.makeText(this, "취소되었습니다.", Toast.LENGTH_SHORT).show() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.os.Bundle | ||
import android.widget.TextView | ||
import androidx.activity.enableEdgeToEdge | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.view.ViewCompat | ||
import androidx.core.view.WindowInsetsCompat | ||
|
||
class ContactDetailActivity : AppCompatActivity() { | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_contact_detail) | ||
|
||
val contact = intent.getSerializableExtra("contactDetail") as Contact | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이부분 또한 아쉬웠어요. Contact 정보가 필요하다면, Contact 를 Parcelable 하게 만드는 것이 조금 더 좋아보입니다. 또한 kotlin-parcelize 를 사용한다면 조금 더 Contact 를 parcelable 하게 만들 수 있습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 parcelable 을 쓰지 않더라도, contact 내부의 값을 모두 따로따로 String 으로 주고받아도 좋을 것 같아요. 예를들어, val name = intent.getStringExtra("name")
val phoneNumber = intent.getStringExtra("phoneNumber")
val email = intent.getStringExtra("email")
// 이하 생략
val contact = Context(
name = name,
phoneNumber = phoneNumber,
email = email,
// 이하 생략
) |
||
|
||
val nameTextView: TextView = findViewById<TextView>(R.id.contact_name) | ||
val phoneNumberTextView: TextView = findViewById(R.id.contact_phone_number) | ||
|
||
nameTextView.text = contact.name | ||
phoneNumberTextView.text = contact.phoneNumber | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.util.Log | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import android.widget.ImageView | ||
import android.widget.TextView | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.content.ContextCompat.startActivity | ||
import androidx.recyclerview.widget.LinearLayoutManager | ||
import androidx.recyclerview.widget.RecyclerView | ||
import java.io.Serializable | ||
|
||
class ContactListActivity : AppCompatActivity() { | ||
private lateinit var contactList: MutableList<Contact> | ||
private lateinit var adapter: ContactRecyclerAdapter | ||
private lateinit var message: TextView | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_contact_list) | ||
|
||
contactList = mutableListOf<Contact>() | ||
|
||
val contactRecyclerView: RecyclerView = findViewById<RecyclerView>(R.id.contact_list_recyclerview) | ||
message = findViewById<TextView>(R.id.add_message) | ||
|
||
adapter = ContactRecyclerAdapter( | ||
contactList = contactList, | ||
inflater = LayoutInflater.from(this@ContactListActivity), | ||
activity = this@ContactListActivity | ||
) | ||
contactRecyclerView.layoutManager = LinearLayoutManager(this@ContactListActivity) | ||
contactRecyclerView.adapter = adapter | ||
|
||
val addButton: ImageView = findViewById<ImageView>(R.id.add_button) | ||
addButton.setOnClickListener{ | ||
val intent: Intent = Intent(this@ContactListActivity, ContactAddActivity::class.java) | ||
startActivityForResult(intent, 1) | ||
} | ||
} | ||
|
||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | ||
when(requestCode){ | ||
1 -> { | ||
when(resultCode){ | ||
AppCompatActivity.RESULT_OK -> { | ||
val contact: Contact? = data?.getSerializableExtra("contactInfo") as Contact? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 흥미로운 주제네요. kotlin 의 safe cast 와 nullable cast 의 충돌인데, 한번 해당 링크 확인해보면 좋을 것 같네요 :) |
||
contact?.let { | ||
contactList.add(it) | ||
adapter.notifyItemInserted(contactList.size-1) | ||
} | ||
message.visibility=View.GONE | ||
} | ||
} | ||
} | ||
} | ||
super.onActivityResult(requestCode, resultCode, data) | ||
} | ||
} | ||
|
||
class Contact( | ||
val name: String, | ||
val phoneNumber: String, | ||
val email: String?, | ||
birthDate: String?, | ||
gender: String?, | ||
memo: String? | ||
) : Serializable | ||
|
||
class ContactRecyclerAdapter( | ||
private val contactList: MutableList<Contact>, | ||
private val inflater: LayoutInflater, | ||
private val activity: ContactListActivity | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. activity 를 변수로 넘기는 것은 금기시해야할 것 중 하나입니다. inflater 또한 파라미터로 받을 필요는 없을 것 같구요. interface OnContactClickListener {
fun onContactClicked(position: Int)
} |
||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | ||
|
||
inner class ViewHolder(val itemView: View) : RecyclerView.ViewHolder(itemView) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거 굳이 inner class 로 사용하는 이유가 있을까요? |
||
val nameInitialTextView: TextView | ||
val nameTextView: TextView | ||
init { | ||
nameInitialTextView = itemView.findViewById(R.id.name_initial_text) | ||
nameTextView = itemView.findViewById(R.id.name_text) | ||
|
||
itemView.setOnClickListener { | ||
val position = adapterPosition | ||
if (position != RecyclerView.NO_POSITION) { | ||
val contact = contactList[position] | ||
val intent = Intent(activity, ContactDetailActivity::class.java) | ||
intent.putExtra("contactDetail", contact) | ||
activity.startActivity(intent) | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { | ||
return ViewHolder(inflater.inflate(R.layout.item_contact, parent, false)) | ||
} | ||
|
||
override fun getItemCount(): Int { | ||
return contactList.size | ||
} | ||
|
||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { | ||
val contact = contactList.get(position) | ||
(holder as ViewHolder).nameInitialTextView.text = contact.name.substring(0 until 1) | ||
(holder as ViewHolder).nameTextView.text = contact.name | ||
} | ||
|
||
} | ||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<androidx.appcompat.widget.LinearLayoutCompat 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/main" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
tools:context=".ContactAddActivity" | ||
android:orientation="vertical" | ||
android:gravity="center_horizontal" | ||
android:layout_weight="1" | ||
android:background="@color/white"> | ||
|
||
<ImageView | ||
android:layout_width="80dp" | ||
android:layout_height="80dp" | ||
android:src="@drawable/contacts_book"/> | ||
<androidx.appcompat.widget.LinearLayoutCompat | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_margin="10dp"> | ||
<TextView | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:hint="이름" | ||
android:textSize="25dp" | ||
android:textColor="@color/black"/> | ||
<TextView | ||
android:id="@+id/contact_name" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:hint="이름" | ||
android:layout_marginLeft="10dp" | ||
android:textSize="25dp" | ||
android:textColor="@color/black"/> | ||
|
||
</androidx.appcompat.widget.LinearLayoutCompat> | ||
|
||
|
||
<androidx.appcompat.widget.LinearLayoutCompat | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_margin="10dp"> | ||
<TextView | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:hint="전화번호" | ||
android:textSize="25dp" | ||
android:textColor="@color/black"/> | ||
<TextView | ||
android:id="@+id/contact_phone_number" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:hint="전화번호" | ||
android:layout_marginLeft="10dp" | ||
android:textSize="25dp" | ||
android:textColor="@color/black"/> | ||
|
||
</androidx.appcompat.widget.LinearLayoutCompat> | ||
|
||
</androidx.appcompat.widget.LinearLayoutCompat> | ||
|
||
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기본적으로 코틀린은 Kotlin coding convention 을 따르게 됩니다.
코딩 컨벤션을 따르게 된다면 다른 리뷰어가 지은님의 코드를 보게될 때 비교적 쉽게 코드를 읽게 도와줍니다.
(https://velog.io/@productuidev/coding-conventionstyle-guide)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
또한 ViewBinding 이라는 것이 있어요.
findViewById 의 번거로움을 획기적으로 줄여줄 뿐더러, null safety, type safety 하다는 장점을 가지기도 합니다.
ViewBInding 을 사용하신다면 조금 더 코드작성이 수월해질 것으로 예상됩니다.