Giter Club home page Giter Club logo

rvcompose's Introduction

RVcompose

Codacy Badge Download License

RVcompose: an easy-to-use, extensible Kotlin DSL for building dynamic reusable UI components with RecycerView


Why

A lot of applications follow a reusable UI pattern of design. I realized most design can be divided into reusable component. Instead of having to design the layout of different screens, I resulted in designing layout for specific UI requirment for reusability . I have tried this approach in a couple of applications (see example). It works great and allows me to build and add a new screen faster. It also helped improved updating views with changes in Models. It also fixes the issue with nested scroll performance when you combine things

RVcompose is an underlying library that provides a structure for designing other applications using this pattern.

Gradle Dependency

Add this to your module's build.gradle file:

dependencies {

  implementation 'xyz.belvi:rvcompose:rvcomposelibrary:1.0.3'
}

The Basics

First, a layout:

<LinearLayout ...>

  <TextView 
     android:id="@+id/text_name"
     ... />    
     
  <TextView 
     android:id="@+id/text_age"
     ... />
     
</LinearLayout>

Second, a View Holder:

data class InputField(
    var hint: String = "",
    var text: String = ""
) :
    Field( R.layout.item_input) {


    override fun getValue(): String {
        return text
    }

    override fun bind(
        itemView: View,
        uiComposeAdapter: UIComposeAdapter,
        position: Int,
        event: (field: Field) -> Unit
    ) {
        itemView.item_field.setText(text)
        itemView.item_field.hint = hint

    }

    override fun hasValidData(): Boolean {
        return validation?.invoke() ?: kotlin.run { if (required) !text.isBlank() else true }
    }

}

Finally, you can begin using the DSL API:

val rv = recycler.compose {
            withLayoutManager(LinearLayoutManager(this@MainActivity, RecyclerView.VERTICAL, false))
            withField<InputField> {
                hint = "Customer Email"
                key = "email"
                required = true
                validation = { Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }

            }
            fieldEvent { uiComposeAdapter, field, position ->

            }
        }

There are two main components of RVcompose:

The Model that describe how your views should be displayed in the RecyclerView.

The Adapter where the models are used to describe what items to show , with what data and interaction between models.

Creating Models

Models are created by extending Field

data class NoteField(
    override val layout: Int = R.layout.item_input_note,
    var hint: String="",
    var text: String = ""
) :
    Field() {
    override fun getValue(): String {
    
    }

    override fun bind(
        itemView: View,
        uiComposeAdapter: UIComposeAdapter,
        position: Int,
        event: (field: Field) -> Unit
    ) {
   

    }

    override fun hasValidData(): Boolean {
        
    }
}

Understanding the fields in a Model

layout The layout to be inflated. It is the only required field when Fieid is extended

data class AdditemField(
    var text: String = ""
) :
    Field(R.layout.item_invoice_add_new_item)

this example shows executing email validation on InputField.

Validation This is important when building Forms or if you need to validated entry in the model.

rvField<InputField> {
                hint = "Customer Email"
                validation = { android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }
            }

this example shows executing email validation on InputField.

Key for referencing a model from the adapter.

rvField<InputField> {
                hint = "Customer Email"
                key = "EMAIL_KEY"
                validation = { android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }
            }

required for marking a field as required. validation is invoked ony if required is true

rvField<InputField> {
                hint = "Customer Email"
                required = true
                key = "EMAIL_KEY"
                validation = { android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }
            }

errormessage message to be displayed when validate fails.

rvField<InputField>
    {
        hint = "Customer Email"
        required = true
        key = "EMAIL_KEY"
        validation = {
            val isEmail = android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches()
            errorMessage = if (isEmail) "" else "enter a valid email address"
            isEmail
        }
    }

datasource Datasource of this model. You can choose to pass some data as paramerter to your model. A datasource can be an database object or any object that has information for updating your view

rvField<InputField>
    {
        hint = "Customer Email"
        required = true
        datasource = Person()
        key = "EMAIL_KEY"
        validation = {
            val isEmail = android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches()
            errorMessage = if (isEmail) "" else "enter a valid email address"
            isEmail
        }
    }

matchSearch() Function for adding your model to a search result. UIComposeAdapter has model search implementation. To ensure your a model is considered while performing a search, override this function.

override fun matchSearch(text: String): Boolean {
        return this.text.contains(text)
    }

getValue(): Any Override the method to set the value the model should return.

data class InvoiceDateField(
    var hint: String ="",
    var date: Calendar = Calendar.getInstance()
) :
    Field( R.layout.item_invoice_date) {

    @SuppressLint("SimpleDateFormat")
    override fun getValue(): String {
        return SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'").format(Date(date.timeInMillis))
    }

These fields are the basic requirements. You can extend Field and build upon the implmentation


Building Models

Models are built in 2 ways when setting up rvCompose.

1. withField

Used this when initialising rvCompose. This usecase applies if views are statics or known during compile time.

 val rv = recycler.compose {
            withLayoutManager(LinearLayoutManager(this@MainActivity, RecyclerView.VERTICAL, false))
            withField<InputField> {
                hint = "Customer Email"
                key = "EMAIL_KEY"
                required = true
                validation = { Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }
            }
            withField<InputField> {
                hint = "Customer Name"
                key = "NAME_KEY"
                required = true
                validation = { this.text.isNotBlank()}
            }
            fieldClicked { uiComposeAdapter, field, position ->

            }
        }

2. rvField

There are cases that requires you to build your models on runtime and update the views. In cases like this, I suggest building a factory to handle this:

object CustomerFactory {
    fun sampleUI(): MutableList<Field> {
        return mutableListOf<Field>().withFields {

            this += rvField<InputField> {
                hint = "Customer Email"
                key = "email"
                required = true
                validation = { android.util.Patterns.EMAIL_ADDRESS.matcher(this.text).matches() }
            }
            this += rvField<InvoiceDateField> {
                hint = "Invoice Date"
                date = Calendar.getInstance()

            }
            this += rvField<SpinnerField> {
                items = mutableListOf("Pay with Cash", "Pay with Card", "Pay some other time")
            }
            this += rvField<InvoiceDateField> {
                hint = "Due Date"
                date = Calendar.getInstance()

            }
            this += rvField<AdditemField> {
                text = "Add Item"
            }

            this += rvField<InvoiceReceiptField> {
                totalDue = 0.0
            }
            this += rvField<NoteField> {
               hint="additional information for customer"
            }
            this+= rvField<ActionField> {
                text = "Create Invoice"
            }

        }


    }


}

this factory returns a list of models MutableList<Field> the ui is updated with :

rv.getAdapter().updateFields(CustomerFactory.sampleUI())

Interaction between Models and Updating Models.

interaction between Fields can happen in bind(). This provide UIComposeAdapter that you can use to retrieve a model by key or index, update the model and reflect changes on view by any of RecyclerView notifyAdapter functions.

override fun bind(
        itemView: View,
        uiComposeAdapter: UIComposeAdapter,
        position: Int,
        event: (field: Field) -> Unit
    ) {

        itemView.btn_action_field.text = text
        itemView.btn_action_field.setOnClickListener {
            (uiComposeAdapter.fieldWithKey("email") as? InputField)?.let {
                it.text = "button clicked"
                uiComposeAdapter.notifyItemChanged(uiComposeAdapter.fieldIndexWithKey(key))
            }
            
        }

    }

Handling Event

To receive field event on fieldEvent, a Field needs to call event(this) on onclick of the view on. See this:

override fun bind(
        itemView: View,
        uiComposeAdapter: UIComposeAdapter,
        position: Int,
        event: (field: Field) -> Unit
    ) {
        itemView.btn_action_field.text = text
        itemView.btn_action_field.setOnClickListener {
          // you can also perform actions here
          event(this) // do this so that it can be received on `fieldEvent`
        }

    }

You can also handle event for all Models in fieldEvent

val rv = recycler.compose {
            withLayoutManager(LinearLayoutManager(this@MainActivity, RecyclerView.VERTICAL, false))
            fieldEvent { uiComposeAdapter, field, position ->
                // event is received perform action 
            }
        }

UICompose Functions

UIComposeAdapter provides functions for interacting and updating the models.

fieldWithKey() retrieve a Model from the adapter with a key.

fieldIndex() retrieve a Model from the adapter with an index (if the index is known)

fieldIndexWithKey() retrieve a Model Index from the adapter with Key

isFormValid() Run validation check on Models in the adapter

formWarning() returns an error message for field with failed validation

formData() Returns Hashmap<key,value> for models in the adapter.

 val rv = recycler.compose {
            withLayoutManager(LinearLayoutManager(this@MainActivity, RecyclerView.VERTICAL, false))
            fieldEvent { uiComposeAdapter, field, position ->
                if (field is ActionField && uiComposeAdapter.isFormValid()) {
                    (uiComposeAdapter.fieldWithKey("email") as InputField).let {
                        it.text = "[email protected]"
                        uiComposeAdapter.notifyItemChanged(uiComposeAdapter.fieldIndexWithKey(key = it.key))
                    }

                } else {
                    Toast.makeText(this@MainActivity, uiComposeAdapter.formWarning(), Toast.LENGTH_LONG).show()
                }
            }
        }

Applications Developed with RVCompose

Traction App

Sample

Contribution

Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.

rvcompose's People

Contributors

kingsmentor avatar codacy-badger avatar

Stargazers

BHawk  avatar Vlad avatar Malcolm Woods avatar sunhapper avatar yongning.yang avatar uni7corn avatar Ilo Calistus avatar Sami Eljabali avatar Jonny Hsia avatar Indexer avatar Jx avatar DannyZhou avatar Piyush Malaviya avatar Kalpana avatar xxc avatar lr avatar aha avatar  avatar  avatar Svran avatar averson avatar Harun Wangereka avatar Petros Paraskevopoulos avatar Alejandro Carbajo Vidales avatar Lucas Liu avatar twiceYuan avatar Cristiano avatar Taslim Oseni avatar Dandan Meng avatar Ifiok Idiang avatar Sui Libin avatar Ahmad Ali avatar  avatar QuincySx avatar Basi avatar Meisam Alifallhi avatar Christoph Kührer avatar Alistair Holmes avatar Iago Mendes Fucolo avatar 脉脉不得语 avatar Obi Ebuka Obi avatar Daeyeon Kim avatar Ismail avatar Christopher Ruz avatar Milad Nekofar avatar Quentin HUET avatar  avatar Gyuri Grell avatar Omept Technology Ltd avatar Mike Diente avatar Karan Trehan avatar Lekan Oladee avatar Patrick Cauley avatar Ememobong Akpanekpo avatar Razak Wasiu avatar bouraoui mansouri avatar Chizoba Ogbonna avatar Jerry Adeleye avatar Ayokunle Paul avatar Zizoh Anto avatar Dhruv Chandrani avatar Nisarg avatar Pranav Lathigara avatar Farouk Sabiou avatar Victor Orie avatar

Watchers

James Cloos avatar  avatar  avatar

rvcompose's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.