Learn how to write a messenger in Kotlin
I started this with instructions from Brain Voong, pretty awesome guy. His full instructions is here
I'm a senior iOS developer, a bit experience in Node.js. I started Android with Ray wenderlich books but I gave up. It made no sense to me. Also tried some tutorial serials in my first language, but no luck.
This course is really awesome. Thanks Brian.
Below is the list of what I learnt from this course.
- Create UI from beginning.
- Connect constraints in ConstraintLayout. Drag and drop, but I prefer setup in code instead.
- Log message:
Log.d("Your tag - Should be your Activity Name", "Your message is here")
. - Start other activity with Intent.
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
- Create and login with Firebase.
- Add depedencies into gradle.build.
- Create round corner textview.
- Create new drawable resource: Should name it like rounded_radius.xml (we reuse it in future, other control type).
- Add code.
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/holo_green_dark"/> <corners android:radius="25dp"/> </shape>
- Change background of the control.
android:background="@drawable/rounded_edittext_register_login"
-
Pick an image
- Show picker activity
val intent = Intent(Intent.ACTION_PICK) intent.type = "image/*" startActivityForResult(intent, 0)
- Get result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 0 && resultCode == Activity.RESULT_OK && data != null) { val selectedPhotoUri = data.data val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedPhotoUri) val bitmapDrawable = BitmapDrawable(bitmap) avatar_imageview_register.setBackgroundDrawable(bitmapDrawable) } }
- Show picker activity
-
Upload to Firebase Storage
val imageName = UUID.randomUUID().toString() val ref = FirebaseStorage.getInstance().getReference("/images/$imageName") ref.putFile(selectedPhotoUri!!) .addOnSuccessListener { val path = it.metadata?.path ref.downloadUrl.addOnSuccessListener { val url = it.toString() saveUserToDb(url) } } .addOnFailureListener { Log.d("Register", it.localizedMessage.toString()) }
-
Create class
User
class User(val uid: String, val userName: String, val avatar: String)
-
CircleImageView implementation
- Add to build.gradle of app level
implementation 'de.hdodenhof:circleimageview:3.1.0'
-
Show new screen and don't allow to go back
val intent = Intent(this, LatestMessagesActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent)
-
Set default activity: change code in manifest file. Add
<intent-filter>
and</intent-filter>
into default activity.<activity android:name=".messages.LatestMessagesActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
-
Show register activity when not logged in. Add code below into onCreate of default activity.
val uid = FirebaseAuth.getInstance().uid if (uid == null) { val intent = Intent(this, RegisterActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) }
-
Add menu
- Add resouce menu file. New/
- Add menu items
<item android:id="@+id/menu_new_message" android:title="New Message" app:showAsAction="ifRoom" /> <item android:id="@+id/menu_sign_out" android:title="Sign Out" app:showAsAction="ifRoom" />
- Show menu in code
override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.nav_menu, menu) return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_new_message -> { val intent = Intent(this, NewMessageActivity::class.java) startActivity(intent) } R.id.menu_sign_out -> { FirebaseAuth.getInstance().signOut() val intent = Intent(this, RegisterActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) } } return super.onOptionsItemSelected(item) }
-
Set LayoutManager in xml instead of in code.
-
Use Groupie to adapt to RecyclerView instead of default way.
class UserItem(val user: User) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.user_name_textview_new_message.text = user.userName
}
override fun getLayout(): Int {
return R.layout.user_row_new_message
}
}
- Use Picasso to download images.
Picasso.get().load(user.avatar).into(viewHolder.itemView.avatar_imageView_new_message)
- Load users from Firebase Real-time database
private fun fetchUsers() {
FirebaseDatabase.getInstance()
.getReference("/users")
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
val user = it.getValue(User::class.java) ?: return
adapter.add(UserItem(user!!))
}
recyclerview_new_message.adapter = adapter
}
override fun onCancelled(p0: DatabaseError) {}
})
}
- Change title
supportActionBar.title = "Chat Log"
- Back button on the action bar doesn't work. Here how to fix.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
}
-
Pass object from Activity to Activity
- From Activity
val userItem = item as UserItem intent.putExtra("USER_KEY", userItem.user.userName) startActivity(intent)
- To Activity, onCreate
val userName = intent.getStringExtra("USER_KEY")
-
Extend a class to Parcelable
- Add code to build.gradle (app) above
android
androidExtensions { experimental = true }
- Add
@Parcelize
and extend Parcelable to class definition
@Parcelize class User(val uid: String, val userName: String, val avatar: String) : Parcelable { constructor() : this("", "", "") }
- Add code to build.gradle (app) above
- Save data to Firebase Database
val ref = FirebaseDatabase.getInstance()
.getReference("/messages")
.push()
ref.setValue(message)
- Unwrap value with Elvis operation
val fromId = FirebaseAuth.getInstance().uid ?: return
- Get current time (in milliseconds)
System.currentTimeMillis()
- Hotkeys
- Search class:
cmd + O
- Search file:
cmd + Shift + O
- Search symbol
cmd + option + O
- Toggle side window:
cmd + number
. For instance: Toggle Projects side bar:cmd + 1
- Collapse all definitions:
cmd + Shift + -
- Collapse function:
cmd + -
- Search class:
- Change to correct message database schema
- Send: save message with 2 to places,
your id
andyour friend id
val ref = FirebaseDatabase.getInstance() .getReference("/user-messages/$fromId/$toId") .push() val toRef = FirebaseDatabase.getInstance() .getReference("/user-messages/$toId/$fromId") .push()
- Send: save message with 2 to places,
- Listen
val ref = FirebaseDatabase.getInstance() .getReference("/user-messages/$fromId/$toId")
- Scroll to latest message:
recyclerview_chat_log.scrollToPosition(adapter.itemCount - 1)
- Chain controls: select 2 controls and right click to select
Chain
. Right click on the chain to selectchain mode
- Send message and save to
latest-messages
. InChatLogActivity
,performSendMessage
val latestMessageRef = FirebaseDatabase.getInstance()
.getReference("/latest_messages/$fromId/$toId")
latestMessageRef.setValue(message)
val latestMessageToRef = FirebaseDatabase.getInstance()
.getReference("/latest_messages/$toId/$fromId")
latestMessageToRef.setValue(message)
- Use HashMap to store latest messages. Add message when
onChildChanged
, andonChildAdded
val latestMessagesMap = HashMap<String, ChatMessage>()
private fun addMessage(p0: DataSnapshot) {
val message = p0.getValue(ChatMessage:: class.javaObjectType) ?: return
latestMessagesMap[p0.key!!] = message
refreshRecyclerView()
}
private fun refreshRecyclerView() {
adapter.clear()
latestMessagesMap.values.forEach {
adapter.add(LatestMessageRow(it))
}
}
This course was closed with not fully features. But it's enough for me. Now I can hop into the company project. The commercial project is really different, too complicated and million lines of code. But I'm confident and I fixed few bugs there. Cheer.
I can do and you can do.
Enjoy coding.