SOLID Components are an attempt at following good engineering standards and best practices such as SOLID and DRY where Google neglected to.
And the implementation dependencies below. You can add just the components you need.
dependencies {
implementation "com.mitteloupe.solid:solidapplication:1.0.8"
implementation "com.mitteloupe.solid:solidactivity:1.0.8"
implementation "com.mitteloupe.solid:solidfragment:1.0.7"
implementation "com.mitteloupe.solid:solidservice:1.0.8"
implementation "com.mitteloupe.solid:solidrecyclerview:1.0.5"
}
Make your app Application instance extend SolidApplication
. To implement functionality, simply override
lifecycleHandlers
, configurationChangeHandlers
or memoryHandlers
, providing relevant handlers.
Use SolidActivity
as the parent activity of any activity in your app. Instead of having a BaseActivity, you can now provide common activity code by overriding one or more of the handler lists, providing a list of handlers.
Common use cases can include dependency injection, analytics, logging, setting up of ViewHolders.
A Koin
injection handler will look as follows:
class KoinActivityScopeHandler(
private val activity: Activity,
private val currentScope: Scope
) : LifecycleHandler {
override fun onCreate(savedInstanceState: Bundle?) {
currentScope.declare(activity)
}
}
Implement an activity using the handler as follows:
class MainActivity : SolidActivity() {
override val lifecycleHandlers = listOf(
KoinActivityScopeHandler(this, currentScope),
...
)
...
}
Use SolidFragment
as the parent fragment of any fragment in your app. Instead of having a BaseFragment, you can now provide common fragment code by overriding one or more of the handler lists, providing a list of handlers.
Common use cases can include dependency injection, analytics, logging, setting up of ViewHolders.
A Koin
injection handler will look as follows:
class KoinFragmentScopeHandler(
private val fragment: Fragment,
private val currentScope: Scope
) : LifecycleHandler {
override fun onCreate(savedInstanceState: Bundle?) {
currentScope.declare(fragment)
}
}
Implement an fragment using the handler as follows:
class MainFragment : SolidFragment() {
override val lifecycleHandlers = listOf(
KoinFragmentScopeHandler(this, currentScope),
...
)
...
}
SolidService
allows composing services instead of inheriting from base services.
As an example, an IntentService
using SolidService
would look like this:
class SolidIntentService : SolidService() {
override val lifecycleHandlers = listOf(
IntentHandler(this, { intent -> handleIntent(intent) })
)
private fun handleIntent(intent: Intent?) {
...
}
}
Instead of setting a RecyclerView.Adapter
to your RecyclerView, simply set a SolidAdapter
.
SolidAdapter
has a few constructor-injected dependencies that define its behaviour:
-
ViewProvider
- this will provide child Views for your RecyclerView. A handyInflatedViewProvider
is available for simple layout inflation. -
viewHolderProvider - this is a lambda that, given a View, returns a ViewHolder. It is worth noting that when using a
SolidAdapter
, ViewHolder do just that. They hold references to Views (commonly obtained by callingfindViewById()
). This is their sole responsibility. -
ViewBinder
- this will bind a data item to views provided by a ViewHolder. -
itemsSynchronizerProvider - this is a lambda that, given a
RecyclerView.Adapter
, returns anItemsSynchronizer
. The responsibility ofItemsSynchronizer
is to hold the data items and synchronize changes with theRecyclerView.Adapter
. If not provided, theSolidAdapter
uses a defaultSimpleItemsSynchronizer
, which provides most common functionality. -
positionToType - this is a lambda that, given an
ItemsSynchronizer
instance and a position, returns the view type for that position. By default, it always returnsITEM_TYPE_DEFAULT
.
Let's take a look at a simple, common RecyclerView.Adapter implementation:
Without SolidAdapter:
class MoodViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
val iconView: ImageView = layoutIconView
val titleView: TextView = layoutTitleView
override fun bindData(moodItem: MoodUiModel) {
iconView.setImageDrawable(
AppCompatResources.getDrawable(context, data.iconResourceId)
)
indexView.text = data.title
}
}
class ListItemsAdapter(
private val layoutInflater: LayoutInflater
) : RecyclerView.Adapter<MoodViewHolder>() {
private val listData = mutableListOf<MoodUiModel>()
fun setData(listData: List<MoodUiModel>) {
this.listData.clear()
this.listData.addAll(listData)
notifyDataSetChanged()
}
fun removeItem(position: Int) {
listData.removeAt(position)
notifyItemRemoved(position)
}
fun addItem(position: Int, item: ListItemUiModel) {
listData.add(position, item)
notifyItemInserted(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoodViewHolder {
val view = layoutInflater.inflate(R.layout.item_mood, parent, false)
return MoodViewHolder(view)
}
override fun getItemCount() = listData.size
override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) {
holder.bindData(listData[position])
}
}
val adapter = ListItemsAdapter(layoutInflater)
With SolidAdapter:
class MoodViewProvider(
layoutInflater: LayoutInflater
) : InflatedViewProvider(layoutInflater, R.layout.item_mood)
class MoodViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
val iconView: ImageView = layoutIconView
val titleView: TextView = layoutTitleView
}
class MoodViewBinder(
private val context: Context
) : SimpleViewBinder<MoodViewHolder, MoodUiModel>() {
override fun bindView(viewHolder: MoodViewHolder, data: MoodUiModel) {
viewHolder.iconView.setImageDrawable(
AppCompatResources.getDrawable(context, data.iconResourceId)
)
viewHolder.indexView.text = data.title
}
}
val adapter = SolidAdapter(
MoodViewProvider(layoutInflater),
{ view, _ -> MoodViewHolder(view) },
MoodViewBinder(this)
)
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.