Ever had some bad issues, while doing an API call in your android application? Or may be got some error which was making your API call unsuccessful.
What did you try?
First resort would have been to try API calls through some client that what was the error. Or may be something else you would have tried.
What if I told you that you could have chosen something else as well and that would have not required you to test APIs via some client.
You can use Interceptors in your android code to gets the logs their itself while getting an error. This sounds a better solution right?
Let’s understand more about logging in Android. So, in this, we will discuss about okhttp logging interceptor and how we can leverage it log our API calls.
What is OkHttp?
OkHttp is an interceptor which helps you to log your API calls. So, here an interceptor is more like a manager for an API call which helps you to monitor or perform certain action on your API calls.
Let us start by including it in our project, we will add the following in the build.gradle file:
override fun onResponse(response: Response?) { //Do something with response }
})
Now, we have performed the first API call. But here we would not see any logs coming up because we have not added any specific interceptor to log the calls. So, how can we log the response the call.
To, start logging we need to add interceptors in the above OkHttpClient.
And as we mentioned , interceptors are used to monitor the API call and it will print the logs which would get generated, in the Logcat of the console.
we create and variable called logging of HttpLoggingInterceptor and set the level of logging to *Basic.* Basic is the initial level in which you can just log reqest and the response of the API. We can also have NONE, HEADERS and BODY.
*NONE : Logs Nothing.*
*HEADERS : Logs Request and Response along with Header.*
*BODY : Logs Request and Response along with header and if body present in the API call.*
And, to add this interceptor to the client we use,
Updates: symfony/service-contracts:v2.0.1, symfony/mime:v5.0.2, symfony/css-selector:v5.0.2, symfony/translation-contracts:v2.0.1 - Updating symfony/service-contracts (v1.1.8 => v2.0.1): The following exception is caused by a lack of memory or swap, or not having swap configured Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details
YAMLException: can not read a block mapping entry; a multiline key may not be an implicit key at line 2, column 5: date: 2019-08-21 17:19:49 ^ at generateError (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:167:10) at throwError (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:173:9) at readBlockMapping (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:1073:9) at composeNode (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:1359:12) at readDocument (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:1519:3) at loadDocuments (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:1575:5) at Object.load (F:\Document\Blog\helloword\node_modules\js-yaml\lib\js-yaml\loader.js:1596:19) at parseYAML (F:\Document\Blog\helloword\node_modules\hexo-front-matter\lib\front_matter.js:80:21) at parse (F:\Document\Blog\helloword\node_modules\hexo-front-matter\lib\front_matter.js:56:12) at Promise.all.spread (F:\Document\Blog\helloword\node_modules\hexo\lib\plugins\processor\post.js:48:20) at tryCatcher (F:\Document\Blog\helloword\node_modules\bluebird\js\release\util.js:16:23) at Promise._settlePromiseFromHandler (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:509:35) at Promise._settlePromise (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:569:18) at Promise._settlePromise0 (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:614:10) at Promise._settlePromises (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:694:18) at Promise._fulfill (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:638:18) at PromiseArray._resolve (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise_array.js:126:19) at PromiseArray._promiseFulfilled (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise_array.js:144:14) at PromiseArray._iterate (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise_array.js:114:31) at PromiseArray.init [as _init] (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise_array.js:78:10) at Promise._settlePromise (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:566:21) at Promise._settlePromise0 (F:\Document\Blog\helloword\node_modules\bluebird\js\release\promise.js:614:10)
② css预处理器主要为css解决浏览器兼容、简化CSS代码 等问题(*Sass诞生于2007年,最早也是最成熟的一款CSS预处理器语言。)
1 2 3 4 5 6 7 8 9 10 11
? Please pick a preset: Manually select features
? Check the features neededforyour project: Router, Vuex, CSS Pre-processors, Linter, Unit
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported bydefault): > SCSS/SASS //Sass安装需要Ruby环境,是在服务端处理的,SCSS 是 Sass3新语法(完全兼容 CSS3且继承Sass功能) LESS //Less最终会通过编译处理输出css到浏览器,Less 既可以在客户端上运行, //也可在服务端运行 (借助 Node.js) Stylus //Stylus主要用来给Node项目进行CSS预处理支持,Stylus功能上更为强壮, //和js联系更加紧密,可创建健壮的、动态的的CSS。
③ ESLint:提供一个插件化的javascript代码检测工具
1 2 3 4 5
? Pick a linter / formatter config: (Use arrow keys) > ESLintwitherror prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier //使用较多
④ 何时检测:
1 2 3
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection) >( ) Lint on save // 保存就检测 ( ) Lint and fix on commit // fix和commit时候检查
⑤ 单元测试 :
1 2
? Pick a unit testing solution: (Use arrow keys) > Mocha + Chai //mocha灵活,只提供简单的测试结构,如果需要其他功能需要添加其他库/插件完成。必须在全局环境中安装 Jest //安装配置简单,容易上手。内置Istanbul,可以查看到测试覆盖率,相较于Mocha:配置简洁、测试代码简 洁、易于和babel集成、内置丰富的expect
CREATE USER 'pma'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'pmapass'; GRANT SELECT, INSERT, UPDATE, DELETE ON <pma_db>.* TO 'pma'@'localhost';
Do you need to add default data to your database, right after it was created or when the database is opened? Use RoomDatabase#Callback! Call the addCallback method when building your RoomDatabase and override either onCreate or onOpen.
onCreate will be called when the database is created for the first time, after the tables have been created. onOpen is called when the database was opened. Since the DAOs can only be accessed once these methods return, we‘re creating a new thread where we’re getting a reference to the database, get the DAO, and then insert the data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Room.databaseBuilder(context.applicationContext, DataDatabase::class.java, "Sample.db") // prepopulate the database after onCreate was called .addCallback(object : Callback() { overridefunonCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // moving to a new thread ioThread { getInstance(context).dataDao() .insert(PREPOPULATE_DATA) } } }) .build()
Note: When using the ioThread approach, if your app crashes at the first launch, in between database creation and insert, the data will never be inserted.
2. Use DAO’s inheritance capability
Do you have multiple tables in your database and find yourself copying the same Insert, Update and Delete methods? DAOs support inheritance, so create aBaseDaoclass, and define your generic @Insert, @Update and @Delete methods there. Have each DAO extend the BaseDao and add methods specific to each of them.
The DAOs have to be interfaces or abstract classes because Room generates their implementation at compile time, including the methods from BaseDao.
3. Execute queries in transactions with minimal boilerplate code
Annotating a method with @Transaction makes sure that all database operations you’re executing in that method will be run inside one transaction. The transaction will fail when an exception is thrown in the method body.
You might want to use the @Transaction annotation for @Query methods that have a select statement, in the following cases:
When the result of the query is fairly big. By querying the database in one transaction, you ensure that if the query result doesn’t fit in a single cursor window, it doesn’t get corrupted due to changes in the database between cursor window swaps.
When the result of the query is a POJO with @Relation fields. The fields are queries separately so running them in a single transaction will guarantee consistent results between queries.
@Delete, @Update and @Insert methods that have multiple parameters are automatically run inside a transaction.
4. Read only what you need
When you’re querying the database, do you use all the fields you return in your query? Take care of the amount of memory used by your app and load only the subset of fields you will end up using. This will also improve the speed of your queries by reducing the IO cost. Room will do the mapping between the columns and the object for you.
Consider this complex User object:
1 2 3 4 5 6 7 8 9
@Entity(tableName = "users") dataclassUser(@PrimaryKey val id: String, val userName: String, val firstName: String, val lastName: String, val email: String, val dateOfBirth: Date, val registrationDate: Date)
On some screens we don’t need to display all of this information. So instead, we can create a UserMinimal object that holds only the data needed.
1 2 3
dataclassUserMinimal(val userId: String, val firstName: String, val lastName: String)
In the DAO class, we define the query and select the right columns from the users table.
5. Enforce constraints between entities with foreign keys
Even though Room doesn’t directly support relations, it allows you to define Foreign Key constraints between entities.
Room has the @ForeignKey annotation, part of the @Entity annotation, to allow using the SQLite foreign key features. It enforces constraints across tables that ensure the relationship is valid when you modify the database. On an entity, define the parent entity to reference, the columns in it and the columns in the current entity.
Consider a User and a Pet class. The Pet has an owner, which is a user id referenced as foreign key.
Optionally, you can define what action to be taken when the parent entity is deleted or updated in the database. You can choose one of the following: NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT or CASCADE, that have same behaviors as in SQLite.
Note: In Room, SET_DEFAULT works as SET_NULL, as Room does not yet allow setting default values for columns.
6. Simplify one-to-many queries via @Relation
In the previousUser-Pet example, we can say that we have a one-to-many relation: a user can have multiple pets. Let’s say that we want to get a list of users with their pets: List.
1 2
dataclassUserAndAllPets (val user: User, val pets: List<Pet> = ArrayList())
To do this manually, we would need to implement 2 queries: one to get the list of all users and another one to get the list of pets based on a user id.
1 2 3 4
@Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId);
We would then iterate through the list of users and query the Pets table.
To make this simpler, Room’s @Relation annotation automatically fetches related entities. @Relation can only be applied to a List or Set of objects. The UserAndAllPets class has to be updated:
1 2 3 4 5 6 7
classUserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() }
In the DAO, we define a single query and Room will query both the Users and the Pets tables and handle the object mapping.
1 2 3
@Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers();
7. Avoid false positive notifications for observable queries
Let’s say that you want to get a user based on the user id in an observable query:
1 2 3 4 5
@Query(“SELECT * FROM Users WHERE userId = :id) fungetUserById(id: String): LiveData<User> // or @Query(“SELECT * FROM Users WHERE userId = :id) fungetUserById(id: String): Flowable<User>
You’ll get a new emission of the User object whenever that user updates. But you will also get the same object when other changes (deletes, updates or inserts) occur on the Users table that have nothing to do with the User you’re interested in, resulting in false positive notifications. Even more, if your query involves multiple tables, you’ll get a new emission whenever something changed in any of them.
Here’s what’s going on behind the scenes:
SQLite supports triggers that fire whenever a DELETE, UPDATE or INSERT happens in a table.
Room creates an InvalidationTracker that uses Observers that track whenever something has changed in the observed tables.
Both LiveData and Flowable queries rely on the InvalidationTracker.Observer#onInvalidated notification. When this is received, it triggers a re-query.
Room only knows that the table has been modified but doesn’t know why and what has changed. Therefore, after the re-query, the result of the query is emitted by the LiveData or Flowable. Since Room doesn’t hold any data in memory and can’t assume that objects have equals(), it can’t tell whether this is the same data or not.
You need to make sure that your DAO filters emissions and only reacts to distinct objects.
If the observable query is implemented using Flowables, use Flowable#distinctUntilChanged.
1 2 3 4 5 6 7 8 9 10 11 12
@Dao abstractclassUserDao : BaseDao<User>() { /** * Get a user by id. * @return the user from the table with a specific id. */ @Query(“SELECT * FROM Users WHERE userid = :id”) protectedabstractfungetUserById(id: String): Flowable<User> fungetDistinctUserById(id: String): Flowable<User> = getUserById(id) .distinctUntilChanged() }
If your query returns a LiveData, you can use a MediatorLiveData that only allows distinct object emissions from a source.
Note: if you’re returning a list to be displayed, consider using the Paging Library and returning a LivePagedListBuilder since the library will help with automatically computing the diff between list items and updating your UI.