background

Jetpack Compose has a Release Candidate in 7.1, and is getting closer to a Release. So what’s the difference between the declarative UI framework and the traditional Android development model? Today we’ll use the long list build as an example of how declarative UIs can improve efficiency.

RecyclerView

The original

We need to write recyclerView. Adapter when using RecyclerView, which is a very repetitive job:

  • createAdapter
  • createViewHolder
  • If you have multiple styles you need to defineViewType
class CustomAdapter(private val dataSet: Array<String>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView

        init {
            textView = view.findViewById(R.id.textView)
        }
    }

    
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
       
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(view)
    }


    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        viewHolder.textView.text = dataSet[position]
    }

    override fun getItemCount(a) = dataSet.size

}
Copy the code

To improve the

Template code can be used to simplify the use of encapsulation, so there are a variety of Adapter encapsulation library, we take the MultiType library of Drakeet as an example, look at how to simplify the construction of RecyclerView.

  • defineViewDelegate
class FooViewDelegate : ViewDelegate<Foo, FooView>() {

  override fun onCreateView(context: Context): FooView {
    return FooView(context).apply { layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) }
  }

  override fun onBindView(view: FooView, item: Foo) {
    view.imageView.setImageResource(item.imageResId)
    view.textView.text = item.text
 
  }
}
Copy the code
  • Registered toMultiTypeAdaper
class SampleActivity : AppCompatActivity() {

  private val adapter = MultiTypeAdapter()
  private val items = ArrayList<Any>()

  override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    val recyclerView = findViewById<RecyclerView>(R.id.list)

    // Register adapteradapter.register(FooViewDelegate()) ... }}Copy the code

As you can see, we only need to focus on building a ViewDelegate(similar to a ViewHolder). We don’t need to write an Adapter, and we don’t need to explicitly define the ViewType (the data type passed in to the ViewDelegate is already there). This greatly reduces the workload in complex long list building scenarios and makes the code easier to maintain.

Jetpack Compose

The original

Let’s start with the basic long list build for Jetpack Compose:

  • Build your own view
@Composable
fun VideoSection(item: Video) {
    Box(modifier = Modifier.padding(top = 5.dp)) { NetworkImage( url = item.cover ? :"",
            modifier = Modifier
                .requiredHeight(height = 220.dp)
                .fillMaxWidth(),
        )
        Image(
            painter = painterResource(id = R.mipmap.video_play),
            "play icon", modifier = Modifier.align(Alignment.Center) ) Text( text = item.title ? :"",
            color = Color.White,
            modifier = Modifier.align(Alignment.BottomStart)
        )
        Text(
            text = "Video", color = Color.White, modifier = Modifier
                .align(Alignment.TopEnd)
                .padding(end = 8.dp)
        )
    }
}
Copy the code
preview
  • Creating a long list
@Composable
fun MainFeedContent(feedItems: List<FeedItem>) {
    LazyColumn(modifier = Modifier.fillMaxWidth()) {
        items(feedItems) {
            when(it){
              is Cover -> CoverSection(item = it)
              is MultiImage -> MultiImageSection(item = it)
              is Video -> VideoSection(item = it)
            }
        }
    }
}
Copy the code

In Jetapck Compose corresponding RecyclerView Lazy *, one of the commonly used have LazyColumn, LazyRow, LazyGrid. See name know meaning, no longer one by one introduction.

Let’s take a look at LazyColumn: It has multiple ways to add items that we can use as needed.

LazyColumn {
    // Add a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Add another single item
    item {
        Text(text = "Last item")
    }
}

@Composable
fun MessageList(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageRow(message)
        }
    }
}
Copy the code

There’s another benefit to using Jetpack Compose’s List: It’s easy to build sticky items:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item ->
            ItemRow(item)
        }
    }
}
Copy the code
The title

Refer to the official documentation for more list usage

You can see if Jetpack Compose is particularly easy to build long lists. You just need to focus on your own view logic, and there’s no complex build steps. That’s the advantage of a declarative UI.

Can you simplify it any more? I have to write a lot of conditions in my LazyColumn. There are 100 types in my LazyColumn. Of course you can simplify! But the type is definitely something to do, it’s just a matter of putting it there, the code moves, the framework comes out… Let’s release the LazyColumn.

To improve the

This scheme registers data types into maps, just like MultiType. But we used to put ViewHolder/Delegate, so that’s a concrete class, and @composable is a function, so how do we store that in a Map? Of course, it is also to define the interface, to instantiate 😹!

  • To define abstract
interface IComposableService<T> {

    val content: @Composable (item: T) -> Unit

    @Suppress("UNCHECKED_CAST")
    @Composable
    fun ComposableItem(item: Any) {
        (item as? T)? .let { content(item) } } }Copy the code
  • Business implementation interface
class CoverImpl : IComposableService<Cover> {
    
    override val content: @Composable (item: Cover) -> Unit ={ item->
        // Concrete view
        CoverSection(item = item)
    }
}

Copy the code
  • buildManager
object ComposableManager {
    private val composableMap = HashMap<String, IComposableService<*>>()

    fun register(key: String, composable: IComposableService< * >) {
        composableMap[key] = composable
    }

    fun getComposable(key: String): IComposableService<*>? {
        return composableMap[key]
    }

}
Copy the code
  • registered
  / / register
  ComposableManager.register(Cover::class.java.name,CoverImpl())
Copy the code
  • use
@Composable
fun MainFeedContent(feedItems: List<FeedItem>) {
    LazyColumn(modifier = Modifier.fillMaxWidth()) {
        items(feedItems) {
             ComposableManager.getComposable(it::class.java.name)?.ComposableItem(it)
        }
    }
}
Copy the code

preview

If you don’t even want to manually write the registration, use service discovery to collect….

Code uploaded to GitHub: Portal

conclusion

As you can see, the declarative approach to building the UI is very convenient, allowing us to focus more on the part of the job we are trying to do. But RecyclerView that nearly 1W lines of code is not blind writing, currently from the performance experience, complex long list or native performance is better. But the problem will be solved, advance knowledge. Finally, make a small advertisement: [Bytedance International E-commerce] sincerely invite front-end, client and back-end.

Send your resume and the position you are interested in to my wechat: BiuBiuQiu0