How to write elegant BaseActivity in Android
- 1 Introduction
- 2. How to implement
- 3. Actual development
1. Introduction
In daily development, I believe that we all have our own BaseActivity for Activity. So after introducing JetPack, how to make our BaseActivity simple and efficient has become a question worth thinking about.
- Technology stack involved
MVVM, JetPack, LiveData, DataBing, ViewModel - Let’s take a look at the final effect first
Isn’t it very simple…
2. How to implement
- MainActivity inherits from BaseActivityVBVM
abstract class BaseActivityVBVM<VB : ViewDataBinding, VM : ViewModel> : BaseActivityVB<VB>, IViewDataBinding<VB> { protected var _factory: ViewModelProvider.Factory? /** * For Hilt (@JvmOverloads kotlin default parameter value is invalid) * @constructor */ constructor() : this(null) constructor(factory: ViewModelProvider.Factory?) : super(){ _factory = factory } // protected lateinit var vm: VM // @CallSuper override fun initLayout() { super.initLayout() vm = UtilKViewModel.get(this, _factory/*, 1*/) bindViewVM(vb) } }
vm is obtained through reflection
- Look at BaseActivityVB again
abstract class BaseActivityVB<VB : ViewDataBinding>( /*protected var _factory: ViewModelProvider.Factory? = null*/ ) : AppCompatActivity(), IActivity, IUtilK { protected val vb: VB by lazy(mode = LazyThreadSafetyMode.NONE) { UtilKViewDataBinding.get<VB>(this::class.java, layoutInflater/*, 0*/).apply { lifecycleOwner = this@BaseActivityVB } } /// @CallSuper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initFlag() initLayout() initData(savedInstanceState) } @CallSuper override fun onDestroy() { vb.unbind() super.onDestroy() } /// @CallSuper override fun initLayout() { setContentView(vb.root) } @CallSuper override fun initData(savedInstanceState: Bundle?) { initView(savedInstanceState) initObserver() } }
Wait?! This is the end. Don’t be impatient. The best part is here.
Where did VB and VM come from –> Reflection
- VM
@JvmStatic @Suppress(CSuppress.UNCHECKED_CAST) fun <VB : ViewDataBinding> get(clazz: Class<*>, inflater: LayoutInflater/*, index: Int = 0*/): VB = UtilKReflectGenericKotlin.getParentGenericTypeByTClazz(clazz, ViewDataBinding::class.java)?.run { getDeclaredMethod("inflate", LayoutInflater::class.java).invoke(null, inflater) as VB } ?: throw Exception("inflate activity vb fail!")
- Due to multi-level inheritance, we also need to consider generic type matching
@JvmStatic fun getParentGenericTypeByTClazz(clazz: Class<*>, parentClazz: Class<*>/*, index: Int = 0*/): Class<*>? = getParentGenericTypeByT(clazz, parentClazz) as? Class<*>? @JvmStatic fun getParentGenericTypeByT(clazz: Class<*>, tClazz: Class<*>/*, index: Int = 0*/): Type? { val superClazz: Class<*>? = clazz.superclass val genericSuperclass: Type? = clazz.genericSuperclass if (genericSuperclass !is ParameterizedType) {//When the inherited class is not a parameterized type, look for it from the parent class return if (superClazz != null) { getParentGenericTypeByT(superClazz, tClazz)//Recursively search for generics when we inherit multi-layer BaseActivity } else null } genericSuperclass.actualTypeArguments.filterIsInstance<Class<*>>() .run { this.printlog()//for debug if (this.isNotEmpty()) { for (clz in this) { if (tClazz.isAssignableFrom(clz)) returnclz } } if (superClazz != null) return getParentGenericTypeByT(superClazz, tClazz) else return null } }
3. Actual development
@AndroidEntryPoint class MainActivity : BaseActivityVBVM<ActivityMainBinding, MainViewModel>() { override fun initView(savedInstanceState: Bundle?) { } override fun bindViewVM(vb: ActivityMainBinding) { vb.vm = vm } }
@HiltViewModel class MainViewModel @Inject constructor(private val _cache: Cache) : BaseViewModel() { var number_gege by VarProperty_Set(_cache.number_gege) { field, value -> lv_number_gege.value = value.toString() _cache.number_gege = value true } var number_meimei by VarProperty_Set(_cache.number_meimei) { field, value -> lv_number_meimei.value = value.toString() _cache.number_meimei = value true } val lv_number_gege = MutableLiveData(_cache.number_gege.toString()) val lv_number_meimei = MutableLiveData(_cache.number_meimei.toString()) fun add_gege(other: Double) { number_gege = number_gege.toBigDecimal().add(BigDecimal(other)).toDouble() } fun minus_gege(other: Double) { number_gege = number_gege.toBigDecimal().minus(BigDecimal(other)).toDouble() } fun add_meimei(other: Double) { number_meimei = number_meimei.toBigDecimal().add(BigDecimal(other)).toDouble() } fun minus_meimei(other: Double) { number_meimei = number_meimei.toBigDecimal().minus(BigDecimal(other)).toDouble() } }
bingo This is all the code, wait, there are some that are not posted, don’t worry, the hot open source address is as follows
1. The open source address of the library in the picture: SwiftKit
1. The open source address of the example project in the picture: LoveCalculator