Firebase 프로젝트 세팅하기

|

Firebase 계정 설정

Firebase Application을 작성하기 위해서는 먼저 Firebase 계정이 필요합니다.

먼저 Firebase Console에 로그인합니다. 그리고 신규 프로젝트 추가를 실행합니다.

image

그리고 화면에서 Android 버튼을 눌러서 안드로이드 앱을 추가합니다.


image

앱 등록 화면에서는 패키지 이름을 넣도록 합시다.


image

구성 파일 다운로드 화면이 나오는데, google-services.json 파일을 다운로드 해서 모듈의 루트 디렉토리에 복사해줍니다.


image

그리고 프로젝트의 build.gradle 파일과 모듈의 build.gradle 파일에 다음과 같은 플러그인을 설정해줍니다.

build.gradle (프로젝트)

buildscript {
  dependencies {
    // Add this line
    classpath 'com.google.gms:google-services:4.0.1'
  }
}

build.gradle (모듈)

build.gradle (프로젝트)

dependencies {
  // Add this line
  implementation 'com.google.firebase:firebase-core:16.0.1'
}

...

// Add to the bottom of the file
apply plugin: 'com.google.gms.google-services'


image

위 단계까지 설정하고 나서 단말에서 어플리케이션을 실행해주면 Firebase 서버에서 어플리케이션 설정이 정상적으로 되었는지 체크합니다.

위 과정을 거치면 본격적으로 Firebase 어플리케이션을 작성할 수 있습니다.

Kotlin Parcelable 인터페이스 구현 예제

|

TodoInfo.kt

import android.os.Parcel
import android.os.Parcelable
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

open class TodoInfo(
        @PrimaryKey
        var id: Long = 0,
        var categoryId: Long = 0,
        var text: String = "",
        var date: Long = 0,
        var done: Boolean = false,
        var seq: Long = 0,
        var isNotification: Boolean = false
) : RealmObject(), Model, Parcelable {
    constructor(categoryId: Long, text: String) : this(0, categoryId, text, 0, false, 0, false)

    private constructor(p: Parcel) : this(
            p.readLong(), p.readLong(), p.readString(), p.readLong(),
            p.readInt() == 1, p.readLong(), p.readInt() == 1
    )

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeLong(id)
        dest?.writeLong(categoryId)
        dest?.writeString(text)
        dest?.writeLong(date)
        dest?.writeInt(if (done) 1 else 0)
        dest?.writeLong(seq)
        dest?.writeInt(if (isNotification) 1 else 0)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<TodoInfo> {
        override fun createFromParcel(parcel: Parcel): TodoInfo {
            return TodoInfo(parcel)
        }

        override fun newArray(size: Int): Array<TodoInfo?> {
            return arrayOfNulls(size)
        }
    }
}


활용 예제(Fragment의 newInstance 메소드 매개변수로 Parcelable 전달하기)

class SampleFragment() : DialogFragment() {

    private lateinit var todoInfo : TodoInfo

    companion object {
        private const val ARG_TODO_INFO = "ARG_TODO_INFO"

        fun newInstance(todoInfo: TodoInfo): SampleFragment {
            return SampleFragment().apply {
                arguments = Bundle().apply {
                    putParcelable(ARG_TODO_INFO, todoInfo)
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            todoInfo = it.getParcelable(ARG_TODO_INFO)
        }
    }
}

Kotlin TimeUtil 및 Log 클래스

|

TimeUtil.kt

import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*

class TimeUtil {
    companion object {

        @Synchronized
        fun getTimeAsLong(): Long {
            val calendar = Calendar.getInstance()
            return calendar.timeInMillis
        }

        @Synchronized
        fun getTimeAsString(format: String): String {
            val date = Date(getTimeAsLong())
            val sdf = SimpleDateFormat(format, Locale.getDefault())

            return sdf.format(date)
        }

        @Synchronized
        fun getTimeAsLong(format: String, text: String): Long {
            try {
                val sdf = SimpleDateFormat(format, Locale.getDefault())
                val date = sdf.parse(text)

                return date.time

            } catch (e: Exception) {
                e.printStackTrace()
            }

            return -1
        }

        @Synchronized
        fun getTimeAsString(format: String, time: Long): String {
            val date = Date(time)
            val sdf = SimpleDateFormat(format, Locale.getDefault())

            return sdf.format(date)
        }
    }
}


Log.kt

class Log {
    companion object {
        private const val TAG = "SampleApp"
        private const val PREFIX = "snowdeer"

        @Synchronized
        fun v(text: String) {
            android.util.Log.v(TAG, getDecoratedLog(text))
        }

        @Synchronized
        fun d(text: String) {
            android.util.Log.d(TAG, getDecoratedLog(text))
        }

        @Synchronized
        fun i(text: String) {
            android.util.Log.i(TAG, getDecoratedLog(text))
        }

        @Synchronized
        fun w(text: String) {
            android.util.Log.w(TAG, getDecoratedLog(text))
        }

        @Synchronized
        fun e(text: String) {
            android.util.Log.e(TAG, getDecoratedLog(text))
        }

        private fun getDecoratedLog(text: String): String {
            val sb = StringBuilder()

            sb.append("[$PREFIX] ")
            sb.append("[${TimeUtil.getTimeAsLong()}] ")
            sb.append(text)

            return sb.toString()
        }
    }
}

Kotlin Drap & Drop 지원하는 RecyclerView (이미지 부분 눌러서 드래그)

|

item_todo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <ImageView
    android:id="@+id/drag_handle"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_marginEnd="20dp"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:src="@drawable/drag_handle"/>

  <TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="15dp"
    android:layout_marginBottom="15dp"
    android:layout_marginStart="10dp"
    android:layout_marginEnd="20dp"
    android:layout_centerVertical="true"
    android:layout_toStartOf="@id/drag_handle"
    android:textAppearance="@style/TextAppearance.AppCompat.Body2"
    android:textSize="18sp"/>

</RelativeLayout>


ItemTouchHelperViewHolder.kt

interface ItemTouchHelperViewHolder {
    fun onItemSelected()
    fun onItemClear()
}


ItemTouItemTouchHelperAdapter.kt

interface ItemTouchHelperAdapter {
    fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
    fun onItemRemove(position: Int)
}


##

OnRecyclerAdapterEventListener.kt

interface OnRecyclerAdapterEventListener {
    fun onItemClicked(position: Int)
    fun onItemLongClicked(position: Int)
    fun onDragStarted(viewHolder: RecyclerView.ViewHolder)
}


SimpleItemTouchHelperCallback.kt

import android.graphics.Canvas
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper

class SimpleItemTouchHelperCallback(val adapter: ItemTouchHelperAdapter) :
        ItemTouchHelper.Callback() {
    private val MAX_ALPHA = 1.0f

    override fun isItemViewSwipeEnabled(): Boolean {
        return false
    }

    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    override fun getMovementFlags(recyclerView: RecyclerView,
                                  viewHolder: RecyclerView.ViewHolder): Int {
        var dragFlags: Int
        var swipeFlags: Int

        if (recyclerView.layoutManager is GridLayoutManager) {
            dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or
                    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
            swipeFlags = 0
        } else {
            dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
            swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        }

        return makeMovementFlags(dragFlags, swipeFlags)
    }

    override fun onMove(recyclerView: RecyclerView,
                        source: RecyclerView.ViewHolder,
                        target: RecyclerView.ViewHolder): Boolean {
        if (source.itemViewType != target.itemViewType) {
            return false
        }

        adapter.onItemMove(source.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, position: Int) {
        adapter.onItemRemove(viewHolder.adapterPosition)
    }

    override fun onChildDraw(c: Canvas, recyclerView: RecyclerView,
                             viewHolder: RecyclerView.ViewHolder,
                             dX: Float, dY: Float, actionState: Int,
                             isCurrentlyActive: Boolean) {

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            val alpha = MAX_ALPHA - Math.abs(dX) / viewHolder.itemView.width
            viewHolder.itemView.alpha = alpha
            viewHolder.itemView.translationX = dX
        } else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
        }
    }

    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?,
                                   actionState: Int) {
        if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder is ItemTouchHelperViewHolder) {
                viewHolder.onItemSelected()
            }
        }

        super.onSelectedChanged(viewHolder, actionState)
    }

    override fun clearView(recyclerView: RecyclerView, viewHolder:
    RecyclerView.ViewHolder) {
        super.clearView(recyclerView, viewHolder)

        viewHolder.itemView.alpha = MAX_ALPHA

        if (viewHolder is ItemTouchHelperViewHolder) {
            viewHolder.onItemClear()
        }
    }
}


TodoListAdapter.kt

import android.content.Context
import android.graphics.Paint
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.ImageView
import android.widget.TextView
import com.ran.todolist.R
import com.ran.todolist.common.TodoInfo
import com.ran.todolist.model.ModelManager
import com.ran.todolist.model.OnTodoInfoEventListener
import com.ran.todolist.utils.Log
import com.ran.todolist.utils.recyclerview.ItemTouchHelperAdapter
import com.ran.todolist.utils.recyclerview.OnRecyclerAdapterEventListener
import kotlinx.android.synthetic.main.item_todo.view.*
import java.util.*
import kotlin.collections.ArrayList

class TodoListAdapter(private val ctx: Context, private val categoryId: Long) :
        RecyclerView.Adapter<ViewHolder>(), OnTodoInfoEventListener,
        ItemTouchHelperAdapter {

    private var list: ArrayList<TodoInfo> = ArrayList()

    private var onEventListener: OnRecyclerAdapterEventListener? = null

    fun setOnRecyclerAdapterEventListener(l: OnRecyclerAdapterEventListener) {
        onEventListener = l
    }

    init {
        ModelManager.instance.addOnTodoInfoEventListener(this)
        refresh()
    }

    private fun refresh() {
        list = ModelManager.instance.getList(categoryId)

        for (i in 0 until list.size) {
            Log.i("snowdeer] ${list[i]}")
        }

        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
        val view: View = LayoutInflater.from(ctx).inflate(R.layout.item_todo, parent, false)
        return ViewHolder(view)
    }

    fun getItem(position: Int): TodoInfo {
        return list[position]
    }

    override fun getItemCount(): Int {
        return list.size
    }

    override fun onModelUpdated(info: TodoInfo) {
        refresh()
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        viewHolder.text.text = list[position].text
        viewHolder.text.paintFlags = when (list[position].done) {
            true -> Paint.STRIKE_THRU_TEXT_FLAG
            else -> 0
        }

        val info = list[position]
        viewHolder.text.setOnLongClickListener {
            Log.i("onItemLongClicked: $position")
            onEventListener?.onItemLongClicked(position)
            true
        }

        viewHolder.handle.setOnTouchListener(View.OnTouchListener { _, event ->
            if (event.action == MotionEvent.ACTION_DOWN) {
                onEventListener?.onDragStarted(viewHolder);
            }
            false;
        })
    }

    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        swap(fromPosition, toPosition)
        return true
    }

    override fun onItemRemove(position: Int) {
        ModelManager.instance.deleteTodoInfo(list[position])
    }

    private fun swap(from: Int, to: Int) {
        ModelManager.instance.swap(list[from], list[to])
        Collections.swap(list, from, to)
        notifyItemMoved(from, to)
    }

}

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val text: TextView = view.text
    val handle: ImageView = view.drag_handle
}


TodoListFragment.kt

import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.ran.todolist.R
import com.ran.todolist.utils.recyclerview.OnRecyclerAdapterEventListener
import com.ran.todolist.utils.recyclerview.SimpleItemTouchHelperCallback
import kotlinx.android.synthetic.main.fragment_todolist.view.*

private const val ARG_TAB_KEY = "ARG_TAB_KEY"

class TodoListFragment : Fragment(), OnRecyclerAdapterEventListener {

    private var categoryId = 0L
    private lateinit var itemTouchHelper: ItemTouchHelper
    private val adapter by lazy {
        TodoListAdapter(activity!!.applicationContext, categoryId)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            categoryId = it.getLong(ARG_TAB_KEY)
        }
    }

    companion object {
        fun newInstance(categoryId: Long): TodoListFragment {
            return TodoListFragment().apply {
                arguments = Bundle().apply {
                    putLong(ARG_TAB_KEY, categoryId)
                }
            }
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_todolist, container, false)

        view.recycler_view.layoutManager = LinearLayoutManager(activity)
        view.recycler_view.adapter = adapter

        adapter.setOnRecyclerAdapterEventListener(this)
        val callback = SimpleItemTouchHelperCallback(adapter)
        itemTouchHelper = ItemTouchHelper(callback)
        itemTouchHelper.attachToRecyclerView(view.recycler_view)

        return view
    }

    override fun onItemClicked(position: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun onItemLongClicked(position: Int) {
        val fragment = TodoDeleteFragment()

        fragment.currentItemId = adapter.getItem(position)?.id
        fragment.show(activity?.supportFragmentManager, "TodoInsertFragment")
    }

    override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
        itemTouchHelper.startDrag(viewHolder)
    }
}

Kotlin Notification 예제

|

NotificationHandler.kt

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import com.ran.todolist.R

class NotificationHandler(private val ctx: Context) {

    private val notificationManager: NotificationManager by lazy {
        ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    }

    companion object {
        private const val CHANNEL_ID = "com.ran.todolist"
        private const val NOTIFICATION_ID = 1001
    }

    fun createNotificationChannel(id: String, name: String, description: String) {
        val importance = NotificationManager.IMPORTANCE_LOW
        val channel = NotificationChannel(id, name, importance)

        channel.description = description
        channel.enableLights(true)
        channel.lightColor = Color.RED
        channel.enableVibration(true)
        channel.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
        notificationManager.createNotificationChannel(channel)
    }

    fun showNotification(contentText: String, resultIntent: Intent) {
        val pendingIntent = PendingIntent.getActivity(ctx, 0, resultIntent, 0)

        val notification = Notification.Builder(ctx, CHANNEL_ID)
                .setContentText(contentText)
                .setSmallIcon(R.drawable.ic_launcher)
                .setChannelId(CHANNEL_ID)
                .setContentIntent(pendingIntent)
                .build()

        notification.flags = Notification.FLAG_NO_CLEAR

        notificationManager.notify(NOTIFICATION_ID, notification)
    }

    fun dismissNotification() {
        notificationManager.cancel(NOTIFICATION_ID)
    }
}


MainActivity.kt

class MainActivity : AppCompatActivity() {
    private val notificationHandler: NotificationHandler by lazy {
        NotificationHandler(applicationContext)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        notificationHandler.createNotificationChannel("ran", "ran", "ran")
        val resultIntent = Intent(this, this@MainActivity::class.java)
        notificationHandler.showNotification("이건 연습이에요", resultIntent)
    }
}