Brick

github gitee

introduce

Assist Android developers to build an annotation processing framework based on the MVVM framework built by JetPack components. The Factory class and lazy method of ViewModel are automatically generated by annotations. Support for injecting ROOM’s DAO layer interface anywhere in the project and the API interface in the Retrofit library.

The characteristics of

Android developers can think of Brick as a lightweight injection framework that is very simple to use and works with 4-6 annotations. The brick works primarily at compile time, incurs no additional performance cost when the App is running, and only 1 annotation library is packaged into your Android project, so don’t worry about bulk.

Scope of application

  1. useandroidxRather thansupportLibrary.
  2. useJetPacktheViewModelComponents.
  3. useRetrofitAs a network request library.
  4. useROOMDatabase framework. (optional)
  5. The server has multiple ports and multiple IP addresses. (optional)

The introduction of

  1. Add the following code to the appropriate location in your Build. gradle file at the root of your Android project:
buildscript { ... Ext {brick_version = '0.2.0'} Repositories {... maven { url 'https://jitpack.io' } } dependencies { classpath "com.gitee.numeron.brick:plugin:$brick_version" } } allprojects { repositories { ... maven { url 'https://jitpack.io' } } }Copy the code
  1. Add the following code to the appropriate location in the build.gradle file of the Android module for which brick is to be enabled in your Android project:
. apply plugin: 'kotlin-kapt' apply plugin: 'brick' ... dependencies { ... implementation "com.gitee.numeron.brick:annotation:$brick_version" kapt "com.gitee.numeron.brick:compiler:$brick_version" }Copy the code

use

How to use @provide annotation

  1. Add the @provide annotation to your ViewModel subclass
@Provide
class WxAuthorViewModel: ViewModel() {
    ...
}
Copy the code
  1. There are three ways to get the brick annotation processor to work:
  • Enter on the Terminalgradlew :[ModuleName]:kaptDebugKotlinRun the script;
  • Find them in the Gradle extension bar on the right side of AndroidStudio[PrjectName] -> [ModuneName] -> Tasks -> other -> kaptDebugKotlinDouble-click to run the script.
  • Ctrl + F9Compile the entire project.

Run the Brick annotation processor in any of these three ways. 3. After the script is run, two package-level methods are generated:

  • lazyWxAuthorViewModel()Extension method, called directly in an Activity or Fragment.
  • get()Method, which can be used to get an instance of the ViewModel when the lazy method is inconvenient.

Note: The lazyWxAuthorViewModel method is just a wrapper around the get() method. Using the generated method directly, you can create the corresponding ViewModel instance:

private val wxAuthorViewModel by lazyWxAuthorViewModel()
Copy the code

Or after onCreate(), with get:

private lateinit var wxAuthorViewModel: WxAuthorViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    wxAuthorViewModel = get(this)
}
Copy the code

2. Use of @inject annotation

-2. (required) add @retrofitInstance to the method that gets the Retrofit instance.

@RetrofitInstance
val retrofit: Retrofit by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(WANDROID_BASE_URL)
        .addConverterFactory(MoshiConverterFactory.create())
        .build()
}

val okHttpClient: OkHttpClient by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    val logInterceptor = HttpLoggingInterceptor()
    logInterceptor.level = HttpLoggingInterceptor.Level.BODY
    OkHttpClient.Builder()
        .addInterceptor(logInterceptor)
        .callTimeout(15, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .connectTimeout(15, TimeUnit.SECONDS)
        .build()
}
Copy the code

Note:@RetrofitInstanceAnnotations can only be marked inpublicModification of thevalAttribute or method,valProperties or methods can be inThe object singletonorcompanion object, or package-level properties/methods.

-1. (Optional) mark @RoomInstance on the properties or methods that get the RoomDatabase instance, such as:

@RoomInstance
val wandroidDatabase: WandroidDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    Room.databaseBuilder(CONTEXT, WandroidDatabase::class.java, "wandroid.db")
        .build()
}
Copy the code

Note:@RoomInstanceAnnotations can only be marked inpublicModification of thevalAttribute or method,valProperties or methods can be inThe object singletonorcompanion object, or package-level properties/methods.

  1. Assumptions have beenRetrofit ApiThe interface andWxAuthorRepoclass
interface WxAuthorApi {
    @GET("wxarticle/chapters/json  ")
    suspend fun getWxAuthorList(): List<WxAuthor>
}

class WxAuthorRepo {
    ...
}

Copy the code
  1. Add in WxAuthorRepolateinit varModification of theWxAuthorApiField, and use@InjectTags:
class WxAuthorRepo {

    @Inject
    lateinit var wxAuthorApi: WxAuthorApi

}
Copy the code
  1. Create it in the ViewModellateinit varModification of theWxAuthorRepoField, and use@InjectTags:
@Provide
class WxAuthorViewModel: ViewModel() {
    @Inject
    private lateinit var wxAuthorRepo: WxAuthorRepo
}
Copy the code

All fields marked by @Inject will be automatically obtained or created at compile time, without worrying about when they are assigned. Note: Do not attempt to assign values to fields that are modified by Lateinit var. This will lead to a fatal error. Note: The only types @inject can be injected are Retrofit’s API and ROOM’s DAO interface, and classes constructed with or without parameters.

Three, multi-server or multi-port processing method:

Suppose you have another Retrofit API interface with a different access address or Port than the one in baseUrl. In this case, you can add @port and @URL annotations to the Retrofit API interface to set their Url or Port.

  1. @PortThe use of:
@Port(1080)
interface ArticleApi {

    @GET("wxarticle/list/{chapterId}/{page}/json")
    suspend fun getArticleList(@Path("chapterId") chapterId: Int, @Path("page") page: Int): Paged<Article>

}
Copy the code

After adding this annotation, brick will create a new Retrofit instance at compile time based on the @RetroFitInstance annotation tagged Retrofit instance and @Port Port number, and use the new Retrofit instance to create an instance of The ArticleApi.

  1. @UrlThe use of:
@Url("http://www.wanandroid.com:1080/")
interface ArticleApi {
    @GET("wxarticle/list/{chapterId}/{page}/json")
    suspend fun getArticleList(@Path("chapterId") chapterId: Int, @Path("page") page: Int): Paged<Article>
}
Copy the code

The implementation principle is the same as @port.

Appendix 1

Wxauthorviewmodels.kt file generated:

/ / kotlin extension methods, in the Activity/fragments by invoking fun ViewModelStoreOwner. LazyWxAuthorViewModel () : Lazy < WxAuthorViewModel > = LazyWeChatAuthorViewModel (this) / / package level method, after the Activity/fragments onCreate method call fun get (the owner: ViewModelStoreOwner): WxAuthorViewModel { val factory = WxAuthorViewModelFactory() return ViewModelProvider(owner, factory).get(WxAuthorViewModel::class.java) } private class WxAuthorViewModelFactory : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <VM : ViewModel> create(clazz: Class<VM>): VM = WxAuthorViewModel() as VM } private class LazyWxAuthorViewModel( private val owner: ViewModelStoreOwner ) : Lazy<WxAuthorViewModel> { private var _value: WxAuthorViewModel? = null override val value: WxAuthorViewModel get() { if(_value == null) { _value = get(owner) } return _value!! } override fun isInitialized(): Boolean = _value ! = null }Copy the code

Appendix 2

Decompiled WxAuthorViewModel.class:

class WxAuthorViewModel extends ViewModel {
	
    private final WxAuthorRepo wxAuthorRepo = new WxAuthorRepo();

}
Copy the code

The appendix 3

Decompiled WxAuthorrepo.class:

class WxAuthorRepo { private final WxAuthorApi wxAuthorApi = RuntimeKt.getRetrofit().create(WxAuthorApi.class); public final WxAuthorApi getWxAuthorApi() { ... return wxAuthorApi; }}Copy the code

Appendix 4

Wxauthorrepo.class with @port annotated WxAuthorApi:

class WxAuthorRepo { private final WxAuthorApi wxAuthorApi = newRetrofit(RuntimeKt.getRetrofit(), 1080, null).create(WxAuthorApi.class); public final WxAuthorApi getWxAuthorApi() { ... return wxAuthorApi; } private final Retrofit newRetrofit(Retrofit retrofit, int port, String url) { if (port > 0) { HttpUrl httpUrl = retrofit.baseUrl().newBuilder().port(port).build(); return retrofit.newBuilder().baseUrl(httpUrl).build(); } else if(url ! = null && url.length() ! = 0) { return retrofit.newBuilder().baseUrl(url).build(); } return retrofit; }}Copy the code

conclusion

After decompiling the code of the class and the whole article, we can draw a general conclusion: Brick is an injection framework implemented by modifying the bytecode of the class after Java is compiled into class and before class is compiled into dex. It assigns values to fields marked @Inject. ViewModel injection is initialized manually by calling the generated method, which is marked with red error on AS before compiling the code. The main task is to make @Inject support ViewModel.