Olx-workshop-gdsc - Build Classified Ads Application With Kotlin

Overview

Build Classified Ads Application

Architecture

Database : [Firebase Realtime Database](Firebase Realtime Database  |  Firebase Documentation)

Setting Up Project

create your 1st application, use BasicActivity template

Slicing Layout

in this case, we want to use just one Activity, and page will use fragment

List of Ads

  1. use firstFragment.kt as listOfAds Fragment

  2. in this part, we use recyclerview to handle list of ads, and floating button to open create ads page

  3. change fragment_first.xml

    <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:context=".FirstFragment">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_list_ads"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_create_ads"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_marginEnd="@dimen/fab_margin"
            android:layout_marginBottom="16dp"
            app:srcCompat="@android:drawable/ic_input_add" />
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
  4. create new layout file to showing the ads item item_ads.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.cardview.widget.CardView android:id="@+id/container_ads"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp"
        app:cardCornerRadius="8dp"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <LinearLayout
            android:padding="16dp"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_title_ads"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Honda Brio 2014"
                android:textAppearance="@style/TextAppearance.AppCompat.Large"
                android:textSize="16sp"
                android:textStyle="bold" />
            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_price_ads"
                android:text="Rp12.000.000"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <androidx.appcompat.widget.AppCompatImageView
                    android:src="@android:drawable/ic_input_get"
                    android:layout_width="20dp"
                    android:layout_height="20dp"/>
    
                <TextView
                    android:id="@+id/tv_location_ads"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ellipsize="end"
                    android:maxLines="1"
                    android:paddingLeft="8dp"
                    android:text="Kembangan, Jakarta Barat"
                    android:textAppearance="@style/TextAppearance.AppCompat.Small"
                    android:textColor="@color/teal_700" />
            </LinearLayout>
        </LinearLayout>
    </androidx.cardview.widget.CardView>
  5. to handle the object of data, we need to create data class

    sample of data structure

    {
        "author": "PT Red Bird",
        "description": "Mau Jual Untung aja nih bos kalo lo mau",
        "location": "Kembangan, Jakarta Barat",
        "price": "Rp130.000.000",
        "title": "Honda Mobilio Ex. Taxi"
    }
    

    create the dataclass with new package models/Ads.kt

    data class Ads(
        var author: String = "",
        var description: String = "",
        var location: String = "",
        var price: String = "",
        var title: String = ""
    )

    because we don't have data source for now, we need to generate dummy data. You can create generator inside Ads data class, so you can modify to be

    companion object {
            fun generateDummyAds(): List<Ads> {
                val listOfAds = arrayListOf<Ads>()
                for (i in 1..10) {
                    if (i%2==0) {
                        listOfAds.add(
                            Ads(
                                author = "Samsul Arifin",
                                description = "OLX Autos adalah solusi terbaik untuk jual mobil secara instan, aman dan nyaman. Dapatkan harga terbaik sesuai dengan kondisi mobil kamu.",
                                location = "Kembangan, Jakarta Barat",
                                price = "Rp175.000.000",
                                title = "Honda Brio RS 2015",
                            )
                        )
                    } else {
                        listOfAds.add(
                            Ads(
                                author = "Hamka Hamzah",
                                description = "Mesin asli bensin\n" +
                                        "4x4 aktif.\n" +
                                        "Surat surat lengkap.\n" +
                                        "Pajak hidup.\n" +
                                        "power steering\n" +
                                        "Sudah pakek pakum rem",
                                location = "Kembangan, Jakarta Barat",
                                price = "Rp75.000.000",
                                title = "Hyundai Atoz 2008",
                            )
                        )
                    }
                }
    
                return listOfAds
            }
        }
  6. Create adapter to handle recyclerview item, you need to AdsAdapter.kt to create Recyclerview Adapter inside adapters/AdsAdapter.kt package [references]

    class AdsAdapter(private val items: List<Ads>, private val listener: AdsListener): RecyclerView.Adapter<AdsAdapter.AdsViewHolder>() {
    
        inner class AdsViewHolder(val binding: ItemAdsBinding): RecyclerView.ViewHolder(binding.root)
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdsViewHolder {
            val binding = ItemAdsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return AdsViewHolder(binding)
        }
    
        override fun onBindViewHolder(holder: AdsViewHolder, position: Int) {
            with(holder) {
                with(items[position]) {
                    binding.tvTitleAds.text = this.title
                    binding.tvLocationAds.text = this.location
                    binding.tvPriceAds.text = this.price
    
                    binding.containerAds.setOnClickListener {
                        listener.onClickAds(this)
                    }
                }
            }
        }
    
        override fun getItemCount(): Int = items.size
    }

    to handle click event to your item, you need to write interface as a callback event, create new interface AdsListener inside adapters package

    interface AdsListener {
        fun onClickAds(ads: Ads)
    }
  7. Modify your firstFragment.kt

    class FirstFragment : Fragment(), AdsListener {
    
        private var _binding: FragmentFirstBinding? = null
        private val binding get() = _binding!!
    
        private val database by lazy { Database() }
    
        private var listOfAds: ArrayList<Ads>  = arrayListOf()
        private lateinit var adsAdapter : AdsAdapter
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
    
            _binding = FragmentFirstBinding.inflate(inflater, container, false)
            adsAdapter = AdsAdapter(listOfAds, this)
            return binding.root
    
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            with(binding.rvListAds) {
                adapter = adsAdapter
                layoutManager = LinearLayoutManager(requireContext())
            }
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            _binding = null
        }
    
        override fun onClickAds(ads: Ads) {
            // TODO: go to detail page
        }
    }
Detail Page
  1. change the layout fragment_second.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".SecondFragment">
    
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_title_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Honda Brio 2014"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            android:textStyle="bold" />
    
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_price_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Rp200.000.000"
            android:layout_marginTop="8dp"
            android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
    
        <LinearLayout
            android:layout_marginTop="4dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <androidx.appcompat.widget.AppCompatImageView
                android:src="@android:drawable/ic_input_get"
                android:layout_width="20dp"
                android:layout_height="20dp"/>
    
            <TextView
                android:id="@+id/tv_location_detail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:paddingLeft="8dp"
                android:text="Kembangan, Jakarta Barat"
                android:textAppearance="@style/TextAppearance.AppCompat.Small"
                android:textColor="@color/teal_700" />
        </LinearLayout>
    
        <View
            android:background="@color/black"
            android:layout_marginVertical="16dp"
            android:layout_width="match_parent"
            android:layout_height="1dp" />
    
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_author_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Oleh: Ahmad Yamin"
            android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
    
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_desc_detail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Dijual Mobil Keren Kece Idaman Anak Muda"
            android:textAppearance="@style/TextAppearance.AppCompat.Small" />
    </LinearLayout>
  2. in the detail page, you just need to show the data which passing from ads list, so you can consume the data from Bundle [references]

  3. modify ads data class as Parcelable, before that you need to add plugins into you gradle app

    plugins {
        ...your default plugin
        id 'kotlin-parcelize'
    }
    
    @Parcelize
    data class Ads(
        var author: String = "",
        var description: String = "",
        var location: String = "",
        var price: String = "",
        var title: String = ""
    ): Parcelable

    handle argument with navigation graph [references]

  4. modify your SecondFragment.kt

    class SecondFragment : Fragment() {
    
        private var _binding: FragmentSecondBinding? = null
        private val binding get() = _binding!!
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
    
            _binding = FragmentSecondBinding.inflate(inflater, container, false)
            return binding.root
    
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            (arguments?.get("ads") as Ads?)?.let { ads ->
                binding.tvTitleDetail.text = ads.title
                binding.tvAuthorDetail.text = ads.author
                binding.tvDescDetail.text = ads.description
                binding.tvPriceDetail.text = ads.price
                binding.tvLocationDetail.text = ads.location
            }
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            _binding = null
        }
    }
Handle From List Page to Detail Page
  1. modify your onClickAds listener to open secondFragment and passing the ads data

    override fun onClickAds(ads: Ads) {
            findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment, bundleOf("ads" to  ads))
        }
Connect to Firebase
  1. simply you can use assistant feature in Android studio to connect your project with Firebase tools -> firebase -> Realtime Database

  2. firebase assistant will open on you sidebar, you just need to click connect to implement firebase in your project.

  3. create class to handle all of database transaction. Create Database.kt inside utils/Database.kt package

    class Database {
        private val path = "ads"
    
        private var database: FirebaseDatabase = FirebaseDatabase.getInstance()
    
        private fun getReference(): DatabaseReference {
            return database.getReference(path)
        }
    
        // function to create new Ads
        fun createAds(ads: Ads) {
            getReference().push().setValue(ads)
        }
    
        // function to read ads
        fun readAds(onSuccess: (List<Ads>) -> (Unit), onError: (Any?) -> Unit) {
            getReference().addValueEventListener(object : ValueEventListener{
                override fun onDataChange(data: DataSnapshot) {
                    val items = arrayListOf<Ads>()
                    for (item: DataSnapshot in data.children) {
                        val adsParser = item.getValue(Ads::class.java)
                        items.add(adsParser!!)
                    }
                    onSuccess(items)
                }
    
                override fun onCancelled(p0: DatabaseError) {
                    onError(p0)
                }
            })
        }
    }
Create "Create Ads" page
  1. create new fragment (class and layout)

  2. create_ads_fragment.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        android:orientation="vertical"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".CreateAdsFragment">
    
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_title"
            android:hint="Title Ads"
            android:layout_marginBottom="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_author"
            android:hint="Yourname"
            android:layout_marginBottom="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_price"
            android:hint="Price"
            android:layout_marginBottom="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_location"
            android:hint="Location"
            android:layout_marginBottom="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_desc"
            android:hint="Description Ads"
            android:lines="4"
            android:layout_marginBottom="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_save"
            android:text="Save Ads"
            android:textAllCaps="false"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

  3. CreateAdsFragment.kt

    class CreateAdsFragment : Fragment() {
        private var _binding: FragmentCreateAdsBinding? = null
        private val binding get() = _binding!!
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            _binding = FragmentCreateAdsBinding.inflate(inflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            binding.btnSave.setOnClickListener {
                // TODO : save item
                val ads = Ads(
                        author = binding.etAuthor.text.toString(),
                        title = binding.etTitle.text.toString(),
                        price = binding.etPrice.text.toString(),
                        location = binding.etLocation.text.toString(),
                        description = binding.etDesc.text.toString()
                    )
                
            }
    
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            _binding = null
        }
    }
  4. Add save functionality, you need to initialization the Database.kt to your fragment

    private val database by lazy { Database() }
    
    
    // modify save button
    binding.btnSave.setOnClickListener {
                // TODO : save item
                val ads = Ads(
                        author = binding.etAuthor.text.toString(),
                        title = binding.etTitle.text.toString(),
                        price = binding.etPrice.text.toString(),
                        location = binding.etLocation.text.toString(),
                        description = binding.etDesc.text.toString()
                    )
                database.createAds(ads)   
            }
Load List Ads from Database

modify your firstFragment.kt

private val database by lazy { Database() }



override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    
    binding.fabCreateAds.setOnClickListener {
         findNavController().navigate(R.id.action_FirstFragment_to_createAdsFragment)
    }
}


override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        database.readAds(
            onError = { err ->
                Toast.makeText(requireContext(), "Error: $err", Toast.LENGTH_SHORT).show()
            } , onSuccess = { items ->
                listOfAds.clear()
                listOfAds.addAll(items)
                adsAdapter.notifyDataSetChanged()
            } )
    }

Result

You might also like...
A starter project to build command-line tools in Kotlin Multiplatform
A starter project to build command-line tools in Kotlin Multiplatform

A starter project to build command-line tools in Kotlin Multiplatform Contains a re-implementation of a real world CLI tool: git-standup Installation

🟣 Opinionated Kotlin libs, DSLs and frameworks to build better web apps

Tegral Tegral is an opinionated collection of Kotlin frameworks, libraries, helpers and DSLs that help you make awesome apps, from web back-ends and b

KVision allows you to build modern web applications with the Kotlin language

KVision allows you to build modern web applications with the Kotlin language, without any use of HTML, CSS or JavaScript. It gives you a rich hierarchy of ready to use GUI components, which can be used as builder blocks for the application UI.

This is a Ktor project to build your own Url shortener
This is a Ktor project to build your own Url shortener

Ktor URL Shortner This project is a implementation for creating Short URL using Ktor + Kotlin + MongoDB Usage It contains two routes if you want to im

Project build to understand the concepts of Git and GitHub

Project build to understand the concepts of Git and GitHub. Summarizing the concepts learnt. Created a project in Android Studio Before installing And

Use Android Jetpack libraries, Android Architecture Components, ViewModel and LiveData to build this app.

Unscramble App Starter code for Android Basics codelab - Store the data in a ViewModel Unscramble is a single player game app that displays scrambled

Build with Jetpack Compose & all modern techniques and architecture of android app development
Build with Jetpack Compose & all modern techniques and architecture of android app development

IMDB Movie App Build with Jetpack Compose & all modern techniques and architecture of android app development ScreenShots 🛠 Built With 🛠 Kotlin - Fi

A project to show the best way to build an Android app on 2022 (by me).
A project to show the best way to build an Android app on 2022 (by me).

Hello Architecture I create this project to show what's for me the best way to build an Android app on 2022. Architecture I use this diagram as and id

Android application compatible with ZX2C4's Pass command line application
Android application compatible with ZX2C4's Pass command line application

Password Store Download Documentation We're in the process of rewriting our documentation from scratch, and the work-in-progress state can be seen her

Owner
Raka Adi Nugroho
Software Engineer @tokopedia
Raka Adi Nugroho
CodeLab for the workshop: A Composable New World

A Composable New World! Compose is here! ?? I've created a codelab where you can follow step by step the development of android application using Comp

Carlos Mota 9 Nov 25, 2022
An attendance recorder app for teachers. Its a part of Android Study Jams 2021 @ GDSC-NITA

Attendance-Pad An attendance recorder app for teachers, build for Android using Kotlin. This project is a part of Andoird Study Jams 2021 @ GDSC-NITA.

Developer Student Clubs - NIT Agartala 2 Jun 23, 2022
LinkHub is a simple and effective link management application that can help you to easily manage your app with no ads!

LinkHub LinkHub is a simple and effective link management application that can help you to easily manage your own links with no ads! Download Screensh

Amr Hesham 71 Dec 17, 2022
A simple textfield for adding quick notes without ads.

Simple Notes A simple textfield for adding quick notes. Need to take a quick note of something to buy, an address, or a startup idea? Then this is the

Simple Mobile Tools 670 Dec 31, 2022
Quick photo and video camera with a flash, customizable resolution and no ads.

Simple Camera A camera with flash, zoom and no ads. The camera is usable for both photo taking and video recording. You can switch between front and r

Simple Mobile Tools 644 Dec 26, 2022
Easy app for managing your files without ads, respecting your privacy & security

Simple File Manager Can also be used for browsing root files and SD card content. You can easily rename, copy, move, delete and share anything you wis

Simple Mobile Tools 1.2k Dec 29, 2022
A simple calendar with events, customizable widgets and no ads.

Simple Calendar A simple calendar with events and a customizable widget. A simple calendar with optional CalDAV synchronization. You can easily create

Simple Mobile Tools 3k Jan 8, 2023
Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Simon Vergauwen 50 Dec 30, 2022
An Android template you can use to build your project with gradle kotlin dsl

Android Gradle KTS An Android template you can use to build your project with gradle kotlin dsl Build.gradle.kts You can use your project's build.grad

Deep 17 Sep 12, 2022