Ad Kits: Adding a Native Ad - Huawei Developers

Adding a Native Ad
Load native ads through the NativeAdLoader class.
Step 1: Building an AdLoader
The NativeAdLoader class provides the NativeAdLoader.Builder class for setting the ad slot ID, customizing attributes, and building the NativeAdLoader object. The sample code is as follows:
Code:
// "testy63txaom86" is a dedicated test ad slot ID.
NativeAdLoader.Builder builder = new NativeAdLoader.Builder(this, "testy63txaom86");
builder.setNativeAdLoadedListener(new NativeAd.NativeAdLoadedListener() {
@Override
public void onNativeAdLoaded(NativeAd nativeAd) {
// Call this method when an ad is successfully loaded.
...
}
}).setAdListener(new AdListener() {
@Override
public void onAdFailed(int errorCode) {
// Called when an ad fails to be loaded.
...
}
});
NativeAdLoader nativeAdLoader = builder.build();
If an ad is loaded successfully, the SDK calls the onNativeAdLoaded() method of NativeAd.NativeAdLoadedListener to return the NativeAd object.
If an ad fails to be loaded, the SDK calls the onAdFailed() method of AdListener.
Step 2: Loading an Ad
NativeAdLoader provides the loadAd() and loadAds() methods for ad loading.
loadAd() for loading a single native ad
AdParam is the only parameter. The sample code is as follows:
Code:
nativeAdLoader.loadAd(new AdParam.Builder().build());
After loadAd() is called, the SDK calls back the added listener method.
AdParam is the only parameter. The sample code is as follows:
nativeAdLoader.loadAd(new AdParam.Builder().build());
After loadAds() is called, the listener makes multiple callbacks based on the number of returned ads. An app requesting multiple ads can use the AdLoader.isLoading() method in the callback code to determine whether ad loading is complete. The sample code is as follows:
Code:
// "testy63txaom86" is a dedicated test ad slot ID.
NativeAdLoader.Builder builder = new NativeAdLoader.Builder(this, "testy63txaom86");
NativeAdLoader nativeAdLoader = builder.setNativeAdLoadedListener(new NativeAd.NativeAdLoadedListener() {
@Override
public void onNativeAdLoaded(NativeAd nativeAd) {
if (adLoader.isLoading()) {
// nativeAdLoader is still loading ads.
...
} else {
// nativeAdLoader has finished loading ads.
...
}
}
}).build();
nativeAdLoader.loadAds(new AdParam.Builder().build(), 5);
Step 3: Displaying an Ad
The "Why this ad" icon and an ad identifier must be displayed in the corner of each ad view.
The procedure for displaying a native ad is as follows:
The following is a sample view hierarchy for a native ad that uses RelativeLayout to display its asset view:
Code:
<com.huawei.hms.ads.nativead.NativeView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
... >
<RelativeLayout
... >
<com.huawei.hms.ads.nativead.MediaView
android:id="@+id/ad_media"
... />
<RelativeLayout
... >
<TextView
android:id="@+id/ad_title"
... />
// Other assets.
...
</RelativeLayout>
// Other assets.
...
</RelativeLayout>
</com.huawei.hms.ads.nativead.NativeView>

Related

How to implement achievements in our Game using Game Service (Kotlin)

More information like this, you can visit HUAWEI Developer Forum​
Original article link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201328115676930048&fid=0101187876626530001
Introduction
In this article I want to show how we can work with Achievements using Game Service, first of all what is an achievement?
Achievements are a great way to increase player engagement in our game and give them greater incentives to continue playing. An achievement can represent a player's achievements (for example, number of players defeated or missions completed) or the skills the player has acquired. We can periodically add achievements to keep the game fresh and encourage players to keep participating. An achievement can contain the following basic attributes:
ID: a unique string generated by AppGallery Connect to identify an achievement.
Name: a short name for the achievement that you define during the achievement setup.
Description: a concise description of the achievement.
Icon: displayed after getting an achievement.
Steps: Achievements can be designated as standard or incremental. An incremental achievement involves a player completing several steps to unlock the achievement. This predefined number of steps is known as the number of achievement steps.
List order: the order in which the current achievement appears among all achievements. It is designated during the achievement setup.
Status: An achievement can be in one of three different states in a game.
Hidden: A hidden achievement means that the details about the achievement are hidden from the player.
Revealed: A revealed achievement means that the player knows about the achievement, but has not earned it yet.
Unlocked: An achievement unlocked means that the player has successfully obtained the achievement.
The HMS Core SDK allow us to unlock an achievement offline. When a game comes back online, it syncs with Huawei's game server to update the achievement's unlocked status.S
Steps
1. How do I set up my Game information in App Gallery Connect?
2. How do I integrate the HMS Core sdk into my project?
3. Create MyApplication class
4. Create Login Activity
5. Create Achievement Adapter
6. Create Game Login Activity
How do I set up my Game information in App Gallery Connect?
To configure your Game in App gallery Connect, you must first create an App with the Game type, generate your signature and configure it on the platform. If you have doubts regarding how to generate the certificate, go to this link and everything will be clearer.
https://developer.huawei.com/consumer/en/codelab/HMSPreparation/index.html#1
How do I integrate the HMS Core sdk into my project?
At first this step seems complex but in reality it is not so complex, because there are really a few steps that we must follow in order to integrate the HMS Core SDK.
Add the necessary dependencies in case of Game Service we require
Code:
dependencies {
implementation 'com.huawei.hms:hwid:{version}'
implementation 'com.huawei.hms:iap:{version}'
implementation 'com.huawei.hms:game:{version}'
}
Add the required plugin
Code:
apply plugin: 'com.huawei.agconnect'
Let's configure the obfuscation scripts
Before building the APK it is good to configure these scripts to prevent the SDK from being obfuscated since if this happens there may be the possibility that HMS Core will not work correctly. To achieve this we must add the following code to our proguard-rules.pro file
Code:
ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
-keep class com.huawei.gamebox.plugin.gameservice.**{*;}
-keep interface com.huawei.hms.analytics.type.HAEventType{*;}
-keep interface com.huawei.hms.analytics.type.HAParamType{*;}
-keep class com.huawei.hms.analytics.HiAnalyticsTools{
public static void enableLog();
public static void enableLog(int);
}
-keep class com.huawei.hms.analytics.HiAnalyticsInstance{*;}
-keep class com.huawei.hms.analytics.HiAnalytics{*;}
Remember to add the signingConfigs on the build.gradle(app)
signingConfigs {
release {
storeFile file("your.jks")
keyAlias 'yourname'
keyPassword 'yourpass'
storePassword 'yourpass'
v1SigningEnabled true
v2SigningEnabled true
}
}
Set up your build Types
Code:
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug{
signingConfig signingConfigs.release
debuggable true
}
}
Create MyApplication class
Let’s create the Application Activity with this class we will be able to set the instance of the application.
Code:
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
HuaweiMobileServicesUtil.setApplication(this);
}
}
Create Login Activity
We are going to create an activity to provide the user an interface login with a Huawei ID our Goal is to create a screen like this one
You can design you own UI but if you want to use mine just copy the code
Code:
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
android:id="@+id/code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="Authorization Code"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Let’s start of Login Creation
Code:
private val AUTH_CODE=100
private val AUTH_TOKEN=200
private val TAG="LoginActivity"
val scopes= listOf(Scope("email"))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
code.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.code ->{
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME)
.setAuthorizationCode()
.setScopeList(scopes)
.createParams()
val service = HuaweiIdAuthManager.getService(this, authParams)
startActivityForResult(service.getSignInIntent(), AUTH_CODE)
}
else -> {}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
AUTH_CODE ->{
val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
//The sign-in is successful, and the user's HUAWEI ID information and authorization code are obtained.
val huaweiAccount = authHuaweiIdTask.result
Log.e(TAG,"Huawei Id ${huaweiAccount.displayName} ${huaweiAccount.avatarUriString} ${huaweiAccount.email}")
Log.e(TAG, "Authorization code:" + huaweiAccount.authorizationCode)
//sendCredentialToServer(huaweiAccount)
jump(huaweiAccount)
} else {
// The sign-in failed.
Log.e(TAG, "sign in failed : " + (authHuaweiIdTask.exception as ApiException).statusCode)
}
}
AUTH_TOKEN ->{
val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
// The sign-in is successful, and the user's HUAWEI ID information and ID token are obtained.
val huaweiAccount = authHuaweiIdTask.result
Log.e(TAG,"Huawei Id ${huaweiAccount.displayName} ${huaweiAccount.avatarUriString} ${huaweiAccount.email}")
Log.i(TAG, "idToken:" + huaweiAccount.idToken)
Log.i(TAG, "image" + huaweiAccount.avatarUri)
jump(huaweiAccount)
} else {
// The sign-in failed. No processing is required. Logs are recorded to facilitate fault locating.
Log.e(TAG, "sign in failed : " + (authHuaweiIdTask.exception as ApiException).statusCode)
}
}
}
}
Create Achievement Adapter
Now we have to create our adapter to populate the recycler View in this case i will use the name Achievment Adapter
Code:
class AchievmentAdapter(val list:ArrayList, val listener:MyVH.OnItemClickListener): RecyclerView.Adapter() {
class MyVH(private val view: View, val listener: OnItemClickListener): RecyclerView.ViewHolder(view),View.OnClickListener {
public fun init(achievement: Achievement){
view.achName.text=achievement.displayName
view.achDesc.text=achievement.descInfo
view.achState.text=when(achievement.state){
3->"Ulocked"
else ->"Locked"
}
view.setOnClickListener(this)
}
public interface OnItemClickListener{
fun onAchievmentClicked(position:Int)
}
override fun onClick(v: View?) {
listener.onAchievmentClicked(adapterPosition)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
val view= LayoutInflater.from(parent.context)
.inflate(R.layout.achievment, parent, false)
return MyVH(view,listener)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: MyVH, position: Int) {
holder.init(list[position])
}
}
As well we need to create the layout for our recycler Row
Code:
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="vertical"
android:elevation="10dp"
android:background="@color/colorPrimary"
>
android:id="@+id/achName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="@color/white"
/>
android:id="@+id/achDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="@color/white"/>
android:id="@+id/achState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="@color/white"/>
Create Game Activity
This is the most important class because in here we will retrive the information from the service
We need to init the service
Code:
private fun init() {
val appsClient = JosApps.getJosAppsClient(this)
appsClient.init()
Log.i("Application", "init success")
}
I have created a method with to get the Achievments in this method we will have the listener which will be in charge to display the information.
Code:
private fun getAchievments() {
val task =
client.getAchievementList(true)
task.addOnSuccessListener(OnSuccessListener { data ->
if (data == null) {
Log.w("Achievement", "achievement list is null")
[email protected]
}
adapter.list.clear()
adapter.list.addAll(data)
adapter.notifyDataSetChanged()
swipe.isRefreshing=false
for(entry in data){
Log.e("Achievment",entry.id)
}
}).addOnFailureListener { e ->
if (e is ApiException) {
val result = "rtnCode:" +
(e as ApiException).statusCode
Log.e("Achievement", result)
}
}
}
now we have to call the methods in the onCreate() Method
Code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
swipe.setOnRefreshListener(this)
swipe.isRefreshing=true
init()
currentPlayerInfo()
client = Games.getAchievementsClient(this)
getAchievments()
adapter= AchievmentAdapter(ArrayList(),this)
recycler.layoutManager=LinearLayoutManager(this)
recycler.adapter=adapter
}
dont forget to declare our variables
Code:
lateinit var client: AchievementsClient
lateinit var adapter: AchievmentAdapter
Wait!!! Do not forget to add the curreontPlayerInfo() which will help us to retrieve HUAWEI ID Information of our User
Code:
private fun currentPlayerInfo() {
val playersClient = Games.getPlayersClient([email protected])
val playerTask = playersClient.currentPlayer
playerTask.addOnSuccessListener { player -> // The request is successful, and the player information is obtained.
name.text=player.displayName
id.text=player.playerId
level.text=player.level.toString()
Picasso.get().load(player.iconImageUri).into(avatarImg);
}.addOnFailureListener { e -> // The player information fails to be obtained.
if (e is ApiException) {
Log.e(TAG, "getPlayerInfo failed, status: " + e.statusCode)
}
}
}
Conclusion
Very good! With this example we have used the the Account kit and Game service Kit to obtain the achievements of a user, I hope this article will be of help to you when developing your video games. Here you have the repo for further information.
https://github.com/ADX2099/Achievment

Pagination for Huawei Site Kit with Paging3

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
Hi everyone, In this article, we’re going to explore how to paginate with the Paging 3 library for Huawei Site Kit and, we’ll use Kotlin in Android Studio. Paging library is a generic solution for pagination. It helps us load and display data from any local or network sources. We’ve used Site Kit as a sample.
Why we need Pagination?
While we get data from any source, pagination provides us getting small chunks of data at a time. That way, we don’t load and display to the user extra data. As a result, we reduce unnecessary network requests and system resource usage.
What is Paging 3 Library?
Paging 3 library is a part of Android Jetpack and enables to load large sets of data gradually. It suits with Android App Architecture and integrates easily with other Jetpack components.
We’ve listed some of the features of the library below.
It supports Kotlin coroutines, Flow, RxJava, and LiveData.
It works with RecyclerView in an integrated manner.
It caches the paged data in-memory to use system resources efficiently.
It makes simple error handling, page refreshing, and retry.
Huawei Site Kit
Site Kit provides place search services including keyword search, nearby place search, place detail search, and place search suggestion, helping your app provide convenient place-related services to attract more users and improve user loyalty.
We’re not going to go into the details of adding Sit Kit to a project. You can follow the instructions to add Site Kit to your project via official docs or codelab.
Our Sample Pagination Project
In this project, we’ll develop a sample app showing nearby places of the user.
You can see the package structure of our application in the image below.
We used MVVM architecture and recent libraries.
Setup the Project
Add the necessary dependencies to build.gradle (app level)
Code:
// HMS Core
implementation 'com.huawei.agconnect:agconnect-core:1.4.2.300'
// Huawei Site Kit
implementation 'com.huawei.hms:site:5.1.0.300'
// Hilt for Dependency Injection
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
// Coroutines for Asynchronous Programming
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Annotation processor
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// to use viewModels() property delegate
implementation "androidx.activity:activity-ktx:1.1.0"
// Paging 3 for pagination
implementation "androidx.paging:paging-runtime:3.0.0-alpha13"
Layout Files
activity_main.xml includes RecyclerView to display nearby places, a progress bar for the loading process, and a button for retry the data.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerSite"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/retryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retry"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
item_site.xml -> We need a item for RecyclerView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="#f9f9f9"
android:orientation="vertical">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_place"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/siteName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent"
tools:text="Eiffel Tower" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/siteDistance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/siteName"
tools:text="800m" />
</androidx.constraintlayout.widget.ConstraintLayout>
Model Classes
We have 2 model classes in our app named Site and Result. Site class is a part of the Site Kit so, we don’t create it.
Result.kt -> Result is a wrapper class which contains Success and Error.
Code:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
Source Of Data
Firstly we need a source to get data so, use the Site Kit as a source. We need a location to use the NearbyLocation feature of the Site Kit and, we selected a mock location near the Eiffel Tower.
HuaweiSiteSource.kt -> With the help of the getNearbyPlaces() function, we get the Site List. This suspend function takes the page number as a parameter. If an error occurs in this process, it returns the Error as a result.
Code:
@Singleton
class HuaweiSiteSource @Inject constructor(@ApplicationContext context: Context) {
private val PAGE_SIZE = 20
private val searchService by lazy {
SearchServiceFactory.create(
context,
URLEncoder.encode(Constant.API_KEY, "UTF-8")
)
}
suspend fun getNearbyPlaces(
page: Int,
): Result<List<Site>> {
val eiffelTowerCoordinate = Coordinate(48.858093, 2.294694)
return suspendCoroutine { cont ->
val radius = 10000
val request = NearbySearchRequest()
request.location = eiffelTowerCoordinate
request.radius = radius
request.pageSize = PAGE_SIZE
request.pageIndex = page
searchService?.nearbySearch(
request,
object : SearchResultListener<NearbySearchResponse> {
override fun onSearchResult(nearbySearchResponse: NearbySearchResponse?) {
val siteList = nearbySearchResponse?.sites
siteList?.let {
cont.resume(Result.Success(it))
}
}
override fun onSearchError(searchStatus: SearchStatus?) {
cont.resume(Result.Error(Exception(searchStatus?.errorMessage)))
}
})
}
}
}
Then, we create the SitePagingSource.kt and implement a PagingSource<Key, Value> to define a data source. It takes a Key and Value as parameters. The Key is the index numbers for pages and, Value is the type of data loaded. We specified Int as the page number, Site as the data type.
PagingSource requires us to implement load() and getRefreshKey() functions.
load() is a suspend function. So, we can make our network or local database requests without blocking the main thread.
getRefreshKey() provides a Key for the initial load for the next Paging Source due to invalidation of this PagingSource. The last accessed position can be retrieved via "state.anchorPosition" so, we used the "state.anchorPosition".
Code:
@Singleton
class SitePagingSource(
private val huaweiSiteSource: HuaweiSiteSource
) : PagingSource<Int, Site>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Site> {
return try {
val page = params.key ?: FIRST_PAGE_INDEX
val result = huaweiSiteSource.getNearbyPlaces(page)
return when (result) {
is Result.Success -> LoadResult.Page(
data = result.data,
prevKey = if (page == FIRST_PAGE_INDEX) null else page - 1,
nextKey = if (result.data.isEmpty() || page >= LAST_PAGE_INDEX) null else page + 1
)
else -> LoadResult.Error(Throwable("Error occurred"))
}
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, Site>): Int? {
return state.anchorPosition
}
companion object {
const val FIRST_PAGE_INDEX = 1
const val LAST_PAGE_INDEX = 3
}
}
HuaweiRepository.kt -> getDefaultPageConfig() provides us to configure our settings such as page size, placeholders, initialloadsize etc.
getSiteListAsFlow() calls the load() method from the SitePagingSource to get data and, we’re transferring the data through the flow.
Code:
@Singleton
class HuaweiRepository @Inject constructor(
private val huaweiSiteSource: HuaweiSiteSource
) {
fun getSiteListAsFlow(
pagingConfig: PagingConfig = getDefaultPageConfig()
): Flow<PagingData<Site>> {
return Pager(
config = pagingConfig,
pagingSourceFactory = { SitePagingSource(huaweiSiteSource) }
).flow
}
private fun getDefaultPageConfig(): PagingConfig {
return PagingConfig(
pageSize = 20,
enablePlaceholders = false
)
}
}
ViewModel
MainViewModel.kt -> fetchSiteList() helps us getting the data and cache it to survive configuration changes like screen rotation.
Code:
@HiltViewModel
class MainViewModel @Inject constructor(
private val huaweiRepository: HuaweiRepository
) : ViewModel() {
fun fetchSiteList(): Flow<PagingData<Site>> {
return huaweiRepository.getSiteListAsFlow()
.cachedIn(viewModelScope)
}
}
Before get into the showing places on the view, I want to mention about RecyclerView Adapter. Paging3 has a special Adapter to list items in the recyclerview.
SiteAdapter.kt -> In this class, we extend our class from PagingDataAdapter. It takes two parameters; a Model class (Site) and a ViewHolder (SiteViewHolder). Also, we used ViewBinding to interact with the views that have an assigned id value in a null-safe and type-safe way. And, we applied the Higher-order function for item clicks.
Code:
class SiteAdapter : PagingDataAdapter<Site, SiteAdapter.SiteViewHolder>(REPO_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SiteViewHolder {
val binding = ItemSiteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SiteViewHolder(binding)
}
companion object {
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<Site>() {
override fun areItemsTheSame(oldItem: Site, newItem: Site): Boolean {
return oldItem.siteId == newItem.siteId
}
override fun areContentsTheSame(oldItem: Site, newItem: Site): Boolean {
return oldItem.equals(newItem)
}
}
}
private var onItemClickListener: ((Site) -> Unit)? = null
override fun onBindViewHolder(holder: SiteViewHolder, position: Int) {
val site = getItem(position) ?: return
holder.binding.apply {
this.siteName.text = site.name
this.siteDistance.text = "${site.distance}m"
}
holder.itemView.apply {
setOnClickListener {
onItemClickListener?.let { it(site) }
}
}
}
inner class SiteViewHolder(val binding: ItemSiteBinding) : RecyclerView.ViewHolder(binding.root)
fun setOnItemClickListener(listener: (Site) -> Unit) {
onItemClickListener = listener
}
}
View
MainActivity.kt -> Firstly, we set up our RecyclerView and SiteAdapter. After that, we collected the data from ViewModel and passed it to the adapter. Finally, we observed the load state with the help of the addLoadStateListener() method. When there is a change in the load state of the adapter, it notifies us. According to these states, we can change our UI status such as loading, displaying, or retrying.
Code:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
private val siteAdapter by lazy {
SiteAdapter().apply {
setOnItemClickListener {
Toast.makeText(
[email protected],
"Clicked Site Name : ${it.name}",
Toast.LENGTH_SHORT
).show()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.recyclerSite.apply {
layoutManager = LinearLayoutManager(context)
adapter = siteAdapter
layoutManager = layoutManager
}
binding.retryButton.setOnClickListener { siteAdapter.retry() }
siteAdapter.addLoadStateListener { loadState ->
when (loadState.source.refresh) {
is LoadState.NotLoading -> {
binding.recyclerSite.isVisible = true
binding.progressBar.isVisible = false
binding.retryButton.isVisible = false
}
is LoadState.Loading -> {
binding.progressBar.isVisible = true
binding.retryButton.isVisible = false
}
is LoadState.Error -> {
binding.progressBar.isVisible = false
binding.retryButton.isVisible = true
}
}
val errorState = loadState.source.append as? LoadState.Error
?: loadState.append as? LoadState.Error
errorState?.let {
Toast.makeText(this, errorState.error.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
lifecycleScope.launch {
viewModel.fetchSiteList().collectLatest { pagingData ->
viewModel.fetchSiteList().distinctUntilChanged().collectLatest {
siteAdapter.submitData(it)
}
}
}
}
Tips & Tricks
⚠While using the Flow, make sure you import correctly. Adding unambiguous imports on the fly in Android Studio can cause the adding of wrong imports.
⚠ Paging3 supports LiveData and RxJava beside Flow.
⚠Configuring your page size relies upon how your data is being loaded and used. Smaller page sizes improve memory usage, latency. Larger pages generally improve loading throughput.
Conclusion
In summary, we have developed a simple app that shows the nearby places of the user. We’ve used the Paging 3 library to make it easier to work with large sets of data and Huawei Site kit to get nearby places information of the users. Please do not hesitate to ask your questions as a comment.
Thank you for your time and dedication. I hope it was helpful. See you in other articles!
References
Paging 3 Official Documentation
Huawei Site Kit Official Documentation

Building a News Client with Network Kit and MVVM

Most android applications download it's content from the cloud (commonly a REST API) getting ready to parse and display that information with lists and menus in order to display dynamic content or provide a personalized experience. There are some third party libraries designed to consume a REST API (like Retrofit) or to download media content (like Glide and Picasso). This time, let me introduce you the new Huawei Network Kit.
What is nework kit?
Network kit is the new Huawei's System SDK designed to simplify the communications with web services by providing 2 main connection modes:
Rest Client
HTTP Client
Network kit supports QUIC connections automatically, that means if the Web service supports QUIC or migrates to QUIC, your app will keep working without require any change. In addition, this kit is pretty similar to the well known Retrofit, so, if you have previous experience with Retrofit, you will be able to integrate Network Kit withount complications.
Previously, we made a News client by using the HQUIC kit. In this article we are going to develop a news client application by using the new Huawei Network Kit.
Previous requirements
A developer account in newsapi.org
Android Studio 4.0 or later and the kotlin plugin
Setting up the project
Network kit doesn't require to setup a project in AGC, but you still need to add the Huawei Maven repositories to your project-level build.gradle:
Java:
buildscript {
ext.kotlin_version = "1.4.31"
repositories {
...
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
...
maven {url 'https://developer.huawei.com/repo/'}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Go to the official documents and look for the Network kit latest version under version change history. Once you have found the latest version available, add it to yout app-level build.gradle as follows
Java:
implementation 'com.huawei.hms:network-embedded:5.0.1.301'
We will use Moshi to parse the response from the web service, let's add the related dependencies and the kapt plugin to proccess the annotations.
Java:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android{
...
}
dependencies {
...
implementation 'com.huawei.hms:network-embedded:5.0.1.301'
implementation 'com.squareup.moshi:moshi:1.11.0'
implementation "com.squareup.moshi:moshi-kotlin:1.11.0"
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.11.0'
...
}
To display the news in a list, we must add RecyclerView and CardView to our project and must enable the DataBinding library to make our job easier.
Java:
android {
...
//Enabling DataBinding and ViewBinding
buildFeatures{
viewBinding true
dataBinding true
}
...
}
dependencies {
...
//MVVM dependencies
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0"
//DataBinding dependency
kapt "com.android.databinding:compiler:3.1.4"
//Layout dependencies
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
...
}
We are ready to start the project.
Building the request
First of all, Network kit must be initialized, let's create an Application class to do this job
NetworkApplication.kt
Java:
class NetworkApplication: Application() {
companion object {
const val TAG="Network Application"
}
override fun onCreate() {
super.onCreate()
initNetworkKit()
}
private fun initNetworkKit() {
// Initialize the object only once, upon the first call.
NetworkKit.init(this ,object : NetworkKit.Callback() {
override fun onResult(result: Boolean) {
if (result) {
Log.i(TAG, "Networkkit init success")
} else {
Log.i(TAG, "Networkkit init failed")
}
}
})
}
}
To make sure this code will be excecuted upon each startup, we must specify this class inside the application element in our AndroidManifest.xml. Let's add the required permissions too.
XML:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".NetworkApplication"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NetworkKitDemo"
android:usesCleartextTraffic="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Now, we must create the data models wich Moshi will use to parse the response
NewsResponse.kt
Java:
@JsonClass(generateAdapter = true)
data class NewsResponse(
@Json(name = "status") val status: String?,
@Json(name = "totalResults") val totalResults: Int?,
@Json(name = "articles") val articles: List<Article>
)
@JsonClass(generateAdapter = true)
data class Article(
@Json(name = "source") val source: Source?,
@Json(name = "author") val author: String?,
@Json(name = "title") val title: String?,
@Json(name = "description") val description: String?,
@Json(name = "url") val url: String?,
@Json(name = "urlToImage") val urlToImage: String?,
@Json(name = "publishedAt") val publishedAt: String?,
@Json(name = "content") val content: String?
)
@JsonClass(generateAdapter = true)
data class Source(
@Json(name = "id") val id: String?,
@Json(name = "name") val name: String?
)
Network kit porvides 2 operation modes, we will use the REST Client to get the Top headlines in the user's country and the HTTP Client mode to download the picture of each Article. We will create a singleton class called NetworkKitHelper.
Let's take a look to the REST Client mode:
NetworkKitHelper.kt
Java:
object NetworkKitHelper {
const val TAG: String = "HTTPClient"
//Your API key from newsapi.org
val apiKey = Keys.readApiKey()
fun createNewsClient(): NewsService {
val restClient = RestClient.Builder()
.httpClient(HttpClient.Builder().build())
.baseUrl("https://newsapi.org/v2/")//Specify the API base URL, this is useful if you will consume multiple paths of the same API
.build()
return restClient.create(NewsService::class.java)
}
//Declare a Request API
interface NewsService {
//Use the GET annotation to specify the path
@GET("top-headlines/")
fun getTopHeadlines(/* use the Query annotation to specify a query parameter in the request*/
@Query("apiKey") apiKey: String? = "",
@Query("country") country: String
): Submit<String?>?
}
fun loadTopHeadlines(sampleService: NewsService, listener: NewsClientListener?,country:String=Locale.getDefault().country) {
sampleService.getTopHeadlines(apiKey,country)?.enqueue(object : Callback<String?>() {
@Throws(IOException::class)
override fun onResponse(submit: Submit<String?>?, response: Response<String?>) {
// Obtain the response. This method will be called if the request is successful.
val body = response.body
body?.let {
try {
val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(NewsResponse::class.java)
val news = adapter.fromJson(it)
news?.let { myNews ->
listener?.onNewsDownloaded(myNews.articles)
}
} catch (e: JSONException) {
Log.e("excepion", e.toString())
}
}
}
override fun onFailure(submit: Submit<String?>?, exception: Throwable?) {
// Obtain the response. This method will be called if the request fails.
Log.e("LoadTopHeadlines", "response onFailure = " + exception?.message)
}
})
}
interface NewsClientListener {
fun onNewsDownloaded(news: List<Article>)
}
}
Put special attention to the loadTopHeadlines function. As you can see, there aren't coroutines or threads defined, we are using the enqueue API instead. By this way Network Kit will handle the request in asynchronous mode for us.
If the API call is successfull, we will use Moshi to parse the response into data objects. By other way, we will be notified about the error in the onFailure callback. Once the response has been parsed, NetworkKitHelper will repor the news to the specified NewsClientListener.
Let's add the code to download the preview pics:
NetworkKitHelper.kt (Adding)
Java:
object NetworkKitHelper {
private val httpClient: HttpClient = createClient()
private fun createClient(): HttpClient {
return HttpClient.Builder()
.callTimeout(1000)
.connectTimeout(10000)
.build()
}
fun createRequest(url: String): Request {
return httpClient.newRequest()
.url(url)
.method("GET")
.build()
}
fun httpClientEnqueue(request: Request, listener: HttpClientListener? = null) {
httpClient.newSubmit(request).enqueue(object : Callback<ResponseBody?>() {
@Throws(IOException::class)
override fun onResponse(
submit: Submit<ResponseBody?>?,
response: Response<ResponseBody?>
) {
// Process the response if the request is successful.
Log.i(TAG, "response code:" + response.code)
response.body?.let {
listener?.onSuccess(it.bytes())
}
}
override fun onFailure(submit: Submit<ResponseBody?>?, throwable: Throwable?) {
// Process the exception if the request fails.
Log.w(TAG, "response onFailure = ${throwable?.message}")
}
})
}
interface HttpClientListener {
fun onSuccess(body: ByteArray)
}
}
As well as with the REST Client mode, we are able to enqueue HTTP Requests and define a callback for each one. In this case, we are receiving a byte array which will be used to create and display a bitmap.
Here we will face a complication. If we try to store the bitmap in the same data class as the Article, Moshi will cause a reflection error at compilation time. To solve this, we will define a new class to store the article and be responsible to load the bitmap, by doing so, we will be able to load the news as soon as we get them and then using the observer pattern, the bitmap will be added to the view as soon as it's ready.
ArticleModel.kt
Java:
class ArticleModel(val article: Article) : NetworkKitHelper.HttpClientListener {
private val _bitmap= MutableLiveData<Bitmap?>().apply{postValue(null)}
val bitmap: LiveData<Bitmap?> =_bitmap
init {
loadBitmap()
}
fun loadBitmap() {
article.urlToImage?.let{
val request=NetworkKitHelper.createRequest(it)
NetworkKitHelper.httpClientEnqueue(request, this)
}
}
override fun onSuccess(body: ByteArray) {
val bitmap= BitmapFactory.decodeByteArray(body, 0, body.size)
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 1000, 600, true)
_bitmap.postValue(resizedBitmap)
}
}
As soon as any instance of ArticleModel is created, it will enqueue an HTTP request async for the preview pic. If the call is successfull, we will receive a ByteArray in the onSuccess callback to create our bitmap from it and let the observer know the bitmap is ready to be displayed.
Sending the request
Let's create a ViewModel which will be responsible to invoke the API and store the data. Here we will use the observer pattern to let the observer know the Articles are ready to be displayed.
MainViewModel.kt
Java:
class MainViewModel : ViewModel(), NetworkKitHelper.NewsClientListener {
private val _articles = MutableLiveData<ArrayList<ArticleModel>>().apply { value = ArrayList() }
val articles: LiveData<ArrayList<ArticleModel>> = _articles
fun loadTopHeadlines(){
articles.value?.let{
if(it.isEmpty()) getTopHeadlines()
else return
}
}
private fun getTopHeadlines() {
NetworkKitHelper.loadTopHeadlines(NetworkKitHelper.createNewsClient(),this)
}
override fun onNewsDownloaded(news: List<Article>) {
val list=ArrayList<ArticleModel>()
for (article: Article in news) {
list.add(ArticleModel(article))
}
_articles.postValue(list)
}
}
To avoid downloading the news again when the user rotates the screen, we are defining the loadTopHeadlines function. It will only make the request if the list of articles is empty.
Displaying the Articles
We will use DataBinding to quicly display our news in a RecyclerView on the MainActivity, let's take a look to the main layout
activity_main.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>
<data class="MainBinding"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_height="match_parent"
android:layout_width="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Now we must define the card wich will be rendered for each article
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
article_card.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data class="ArticleBinding">
<variable
name="item"
type="com.hms.demo.networkkitdemo.ArticleModel" />
</data>
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="5dp"
android:layout_marginVertical="5dp"
card_view:cardCornerRadius="15dp"
card_view:cardElevation="20dp"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/pic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:contentDescription="@string/desc"
card_view:layout_constraintEnd_toEndOf="parent"
card_view:layout_constraintHorizontal_bias="1.0"
card_view:layout_constraintStart_toStartOf="parent"
card_view:layout_constraintTop_toBottomOf="@+id/articleTitle"
card_view:shapeAppearanceOverlay="@style/roundedImageView"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/articleTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{item.article.title}"
android:textAlignment="viewStart"
android:textSize="24sp"
android:textStyle="bold"
card_view:layout_constraintEnd_toEndOf="parent"
card_view:layout_constraintHorizontal_bias="0.498"
card_view:layout_constraintStart_toStartOf="parent"
card_view:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/desc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@{item.article.description}"
android:textSize="20sp"
card_view:layout_constraintEnd_toEndOf="parent"
card_view:layout_constraintStart_toStartOf="parent"
card_view:layout_constraintTop_toBottomOf="@+id/pic" />
<TextView
android:id="@+id/source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@{item.article.source.name}"
card_view:layout_constraintBottom_toBottomOf="parent"
card_view:layout_constraintStart_toStartOf="parent"
card_view:layout_constraintTop_toBottomOf="@+id/desc"
card_view:layout_constraintVertical_bias="0.09" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
The ArticleBinding class will be responsible to fill the view with the values in it's ArticleModel instance for us. That's the magic of DataBinding.
As you may know, to display elements in a RecyclerView we need an Adapter, so let's define it
NewsAdapter.kt
Java:
class NewsAdapter: RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
var articles:List<ArticleModel> =ArrayList()
class NewsViewHolder(private val binding:ArticleBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item:ArticleModel){
binding.item=item
item.bitmap.observe(binding.root.context as LifecycleOwner){
it?.let{
binding.pic.setImageBitmap(it)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val inflater=LayoutInflater.from(parent.context)
val binding=ArticleBinding.inflate(inflater,parent,false)
return NewsViewHolder(binding)
}
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
holder.bind(articles[position])
}
override fun getItemCount(): Int {
return articles.size
}
}
Put special attention to the bind function of the NewsViewHolder class, from here we are telling to the ArticleBinding instance what is the information we want to display in the view. Also, we are using the observer pattern to update the ImageView once the article's preview pic has been downloaded.
Finally, is time to join everything through the MainActivity
MainActivity.kt
Java:
class MainActivity : AppCompatActivity() {
companion object {
const val TAG="Main"
}
private lateinit var binding:MainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= MainBinding.inflate(layoutInflater)
binding.lifecycleOwner = this
setContentView(binding.root)
val viewModel:MainViewModel=ViewModelProvider(this).get(MainViewModel::class.java)
val adapter=NewsAdapter()
viewModel.articles.observe(this){
adapter.articles=it
adapter.notifyDataSetChanged()
}
binding.recycler.adapter=adapter
viewModel.loadTopHeadlines()
}
}
Final result
Tips & Tricks
If your app will consume a REST API with Kotlin, is better to use Moshi instead of gson because Moshi can understand the kotlin's not-nullable types.
If you will use API keys to authenticate your client with the server, is better to use the NDK to hide your KEY and prevent it from being obtained by using reverse engineering. Let's use the Rahul Sharma's hidding method. (Make sure to download the Android NDK from the SDK Manager)
1. Swithch to the Project view and create a jni directory under the main directory.
2. Under the jni directory add the next 3 files:
Android.mk
Code:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := keys
LOCAL_SRC_FILES := keys.c
include $(BUILD_SHARED_LIBRARY)
Application.mk
Code:
APP_ABI := all
Keys.c (Put here your API key)
Code:
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_hms_demo_networkkitdemo_Keys_getApiKey(JNIEnv *env, jclass instance) {
return (*env)->NewStringUTF(env, "PUT_HERE_YOUR_API_KEY");
}
3. Switch back to the Android View and create a Keys kotlin object
Keys.kt
Java:
object Keys {
init {
System.loadLibrary("keys")
}
private external fun getApiKey(): String?
public fun readApiKey(): String? { //use this method for String
return getApiKey()
}
}
4. Tell gradle you will use NDK by adding the next code inside android
build.gradle (app-level)
Java:
plugins {
...
}
android {
...
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
}
dependencies {
...
}
Finally, modify the NetworkKitHelper object to read the API key from the native library.
NetworkKitHelper.kt (Modifying)
Code:
object NetworkKitHelper {
val apiKey = Keys.readApiKey()
}
Conclusion
By using Network kit your app will be ready to perform requests over QUIC or HTTP/2 without writting extra code. The REST Client mode and it's annotations are helpful to to quickly consume a REST API without taking care about Threads or Coroutines. And finally, the HTTP Client mode is useful to download preview images or any other stuff which is not a JSON.
References
Read In Forum
Network Kit official Docs
Hiding Secret/Api key from reverse engineering in Android using NDK
Moshi
Hi, i have one question if we use network kit then we no need to use any third-party like volley, Retrofit?
Is it faster and easy to use than Retrofit library?

Network Operation in Android with Huawei Network Kit

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
Hi everyone, In this article, we’ll take a look at the Huawei Network Kit and how to use it with Rest APIs. Then, we will develop a demo app using Kotlin in the Android Studio. Finally, we’ll talk about the most common types of errors when making network operations on Android and how you can avoid them.
Huawei Network Kit
Network Kit is a service suite that allows us to perform our network operations quickly and safely. It provides a powerful interacting with Rest APIs and sending synchronous and asynchronous network requests with annotated parameters. Also, it allows us to quickly and easily upload or download files with additional features such as multitasking, multithreading, resumable uploads, and downloads. Lastly, we can use it with other Huawei kits such as hQUIC Kit and Wireless Kit to get faster network traffic.
Our Sample Project
In this application, we'll get a user list from a Rest Service and show the user information on the list. When we are developing the app, we'll use these libraries
RecyclerView
DiffUtil
Kotlinx Serialization
ViewBinding
To make it simple, we don't use an application architecture like MVVM and a progress bar to show the loading status of the data.
The file structure of our sample app:
Website for Rest API
JsonPlaceHolder is a free online Rest API that we can use whenever we need some fake data. We’ll use the fake user data from the below link. And, it gives us the user list as Json.
https://jsonplaceholder.typicode.com/users
Why we are going to use Kotlin Serialization instead of Gson ?
Firstly, we need a serialization library to convert JSON data to objects in our app. Gson is a very popular library for serializing and deserializing Java objects and JSON. But, we are using the Kotlin language and Gson is not suitable for Kotlin. Because Gson doesn’t respect non-null types in Kotlin.
If we try to parse such as a string with GSON, we’ll find out that it doesn’t know anything about Kotlin default values, so we’ll get the NullPointerExceptions as an error. Instead of Kotlinx Serialization, you can also use serialization libraries that offer Kotlin-support, like Jackson or Moshi. We will go into more detail on the implementation of the Kotlinx Serialization.
Setup the Project
We’re not going to go into the details of integrating Huawei HMS Core into a project. You can follow the instructions to integrate HMS Core into your project via official docs or codelab. After integrating HMS Core, let’s add the necessary dependencies.
Add the necessary dependencies to build.gradle (app level)
Java:
plugins {
id 'com.huawei.agconnect' // HUAWEI agconnect Gradle plugin'
id 'org.jetbrains.kotlin.plugin.serialization' // Kotlinx Serialization
}
android {
buildFeatures {
// Enable ViewBinding
viewBinding true
}
}
dependencies {
// HMS Network Kit
implementation 'com.huawei.hms:network-embedded:5.0.1.301'
// Kotlinx Serialization
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}
We’ll use viewBinding instead of findViewById. It generates a binding class for each XML layout file present in that module. With the instance of a binding class, we can access the view hierarchy with type and null safety.
We used the kotlinx-servialization-json:1.01 version instead of the latest version 1.1.0 in our project. If you use version 1.1.0 and your Kotlin version is smaller than 1.4.30-M1, you will get an error like this:
Code:
Your current Kotlin version is 1.4.10, while kotlinx.serialization core runtime 1.1.0 requires at least Kotlin 1.4.30-M1.
Therefore, if you want to use the latest version of Kotlinx Serialization, please make sure that your Kotlin version is higher than 1.4.30-M1.
Add the necessary dependencies to build.gradle (project level)
Java:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.huawei.agconnect:agcp:1.4.1.300' // HUAWEI Agcp plugin
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // Kotlinx Serialization
}
}
Declaring Required Network Permissions
To use functions of Network Kit, we need to declare required permissions in the AndroidManifest.xml file.
XML:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Initialize the Network Kit
Let’s create an Application class and initialize the Network Kit here.
Java:
class App : Application() {
private val TAG = "Application"
override fun onCreate() {
super.onCreate()
initNetworkKit()
}
private fun initNetworkKit() {
NetworkKit.init(applicationContext, object : NetworkKit.Callback() {
override fun onResult(result: Boolean) {
if (result) {
Log.i(TAG, "NetworkKit init success")
} else {
Log.i(TAG, "NetworkKit init failed")
}
}
})
}
}
Note: Don’t forget to add the App class to the Android Manifest file.
XML:
<manifest ...>
...
<application
android:name=".App"
...
</application>
</manifest>
ApiClient
getApiClient() -> It returns the RestClient instance as a Singleton. We can set the connection time out value here. Also, we specified the base URL.
Java:
const val BASE_URL = "https://jsonplaceholder.typicode.com/"
class ApiClient {
companion object {
private var restClient: RestClient? = null
fun getApiClient(): RestClient {
val httpClient = HttpClient.Builder()
.callTimeout(1000)
.connectTimeout(10000)
.build()
if (restClient == null) {
restClient = RestClient.Builder()
.baseUrl(BASE_URL)
.httpClient(httpClient)
.build()
}
return restClient!!
}
}
}
ApiInterface
We specified the request type as GET and pass the relative URL as “users”. And, it returns us the results as String.
Java:
interface ApiInterface {
@GET("users")
fun fetchUsers(): Submit<String>
}
User — Model Class
As I mentioned earlier, we get the data as a string. Then, we’ll convert data to User object help of the Kotlinx Serialization library. To perform this process, we have to add some annotations to our data class.
@serializable -> We can make a class serializable by annotating it.
@SerialName() -> The variable name in our data must be the same as we use in the data class. If we want to set different variable names, we should use @SerialName annotation.
Java:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("id")
val Id: Int = 0,
val name: String = "",
val username: String = "",
val email: String = "",
)
UserDiffUtil
To tell the RecyclerView that an item in the list has changed, we’ll use the DiffUtil instead of the notifyDataSetChanged().
DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. And, it uses The Myers Difference Algorithm to do this calculation.
What makes notifyDataSetChanged() inefficient is that it forces to recreate all visible views as opposed to just the items that have changed. So, it is an expensive operation.
Java:
class UserDiffUtil(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].Id == newList[newItemPosition].Id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
}
row_user.xml
We have two TextView to show userId and the userName. We’ll use this layout in the RecylerView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tv_userId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<View
android:id="@+id/divider_vertical"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_userId"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_userName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/divider_vertical"
app:layout_constraintTop_toTopOf="parent"
tools:text="Antonio Vivaldi" />
<View
android:id="@+id/divider_horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
UserAdapter
It contains the adapter and the ViewHolder class.
Java:
class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
private var oldUserList = emptyList<User>()
class UserViewHolder(val binding: RowUserBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder(
RowUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.binding.tvUserId.text = oldUserList[position].Id.toString()
holder.binding.tvUserName.text = oldUserList[position].name
}
override fun getItemCount(): Int = oldUserList.size
fun setData(newUserList: List<User>) {
val diffUtil = UserDiffUtil(oldUserList, newUserList)
val diffResults = DiffUtil.calculateDiff(diffUtil)
oldUserList = newUserList
diffResults.dispatchUpdatesTo(this)
}
}
activity_main.xml
It contains only a recyclerview to show the user list.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
userAdapter - We create a adapter for the RecyclerView.
apiClient - We create a request API object using the RestClient object (ApiClient).
Network Kit provides two ways to send network request: synchronous and asynchronous.
Synchronous requests block the client until the operation completes. We can only get data after it finishes its task.
An asynchronous request doesn’t block the client and we can receive a callback when the data has been received.
getUsersAsSynchronous() - We use synchronous requests here. Firstly, we get the response from RestApi. Then, we need to convert the JSON data to User objects. We use the decodeFromString function to do this. Also, we set ignoreUnknownKeys = true, because we don’t use all user information inside the JSON file. We just get the id, name, username, and email. If you don’t put all information inside your Model Class (User), you have to set this parameter as true. Otherwise, you will get an error like:
Code:
Use ‘ignoreUnknownKeys = true’ in ‘Json {}’ builder to ignore unknown keys.
We call this function inside the onCreate. But, we are in the main thread, and we cannot call this function directly from the main thread. If we try to do this, it will crash and give an error like:
Code:
Caused by: android.os.NetworkOnMainThreadException
We should change our thread. So, we call getUsersAsSynchronous() function inside the tread. Then, we get the data successfully. But, there is still one problem. We changed our thread and we cannot change any view without switching to the main thread. If we try to change a view before switching the main thread, it will give an error:
Code:
D/MainActivity: onFailure: Only the original thread that created a view hierarchy can touch its views.
So, we use the runOnUiThread function to run our code in the main thread. Finally, we send our data to the recyclerview adapter to show on the screen as a list.
getUsersAsAsynchronous() - We use asynchronous requests here. We send a network request and wait for the response without blocking the thread. When we get the response, we can show the user list on the screen. Also, we don’t need to call our asynchronous function inside a different thread. But, if we want to use any view, we should switch to the main thread. So, we use the runOnUiThread function to run our code in the main thread again.
Java:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val TAG = "MainActivity"
private val userAdapter by lazy { UserAdapter() }
private val apiClient by lazy {
ApiClient.getApiClient().create(ApiInterface::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager([email protected])
adapter = userAdapter
}
getUsersAsAsynchronous()
/*
thread(start = true) {
getUsersAsSynchronous()
}
*/
}
private fun getUsersAsSynchronous() {
val response = apiClient.fetchUsers().execute()
if (response.isSuccessful) {
val userList =
Json { ignoreUnknownKeys = true }.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
private fun getUsersAsAsynchronous() {
apiClient.fetchUsers().enqueue(object : Callback<String>() {
override fun onResponse(p0: Submit<String>?, response: Response<String>?) {
if (response?.isSuccessful == true) {
val userList = Json {
ignoreUnknownKeys = true
}.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
override fun onFailure(p0: Submit<String>?, p1: Throwable?) {
Log.d(TAG, "onFailure: ${p1?.message.toString()}")
}
})
}
}
Tips & Tricks
You can use Coroutines to manage your thread operations and perform your asynchronous operations easily.
You can use Sealed Result Class to handle the network response result based on whether it was a success or failure.
Before sending network requests, you can check that you’re connected to the internet using the ConnectivityManager.
Conclusion
In this article, we have learned how to use Network Kit in your network operations. And, we’ve developed a sample app that lists user information obtained from the REST Server. In addition to sending requests using either an HttpClient object or a RestClient object, Network Kit offers file upload and download featuring. Please do not hesitate to ask your questions as a comment.
Thank you for your time and dedication. I hope it was helpful. See you in other articles.
References
Huawei Network Kit Official Documentation
Huawei Network Kit Official Codelab
which permission are required?
AbdurrahimCillioglu said:
View attachment 5273549
Introduction
Hi everyone, In this article, we’ll take a look at the Huawei Network Kit and how to use it with Rest APIs. Then, we will develop a demo app using Kotlin in the Android Studio. Finally, we’ll talk about the most common types of errors when making network operations on Android and how you can avoid them.
Huawei Network Kit
Network Kit is a service suite that allows us to perform our network operations quickly and safely. It provides a powerful interacting with Rest APIs and sending synchronous and asynchronous network requests with annotated parameters. Also, it allows us to quickly and easily upload or download files with additional features such as multitasking, multithreading, resumable uploads, and downloads. Lastly, we can use it with other Huawei kits such as hQUIC Kit and Wireless Kit to get faster network traffic.
Our Sample Project
In this application, we'll get a user list from a Rest Service and show the user information on the list. When we are developing the app, we'll use these libraries
RecyclerView
DiffUtil
Kotlinx Serialization
ViewBinding
To make it simple, we don't use an application architecture like MVVM and a progress bar to show the loading status of the data.
View attachment 5273551
The file structure of our sample app:
View attachment 5273553
Website for Rest API
JsonPlaceHolder is a free online Rest API that we can use whenever we need some fake data. We’ll use the fake user data from the below link. And, it gives us the user list as Json.
https://jsonplaceholder.typicode.com/users
View attachment 5273555
Why we are going to use Kotlin Serialization instead of Gson ?
Firstly, we need a serialization library to convert JSON data to objects in our app. Gson is a very popular library for serializing and deserializing Java objects and JSON. But, we are using the Kotlin language and Gson is not suitable for Kotlin. Because Gson doesn’t respect non-null types in Kotlin.
If we try to parse such as a string with GSON, we’ll find out that it doesn’t know anything about Kotlin default values, so we’ll get the NullPointerExceptions as an error. Instead of Kotlinx Serialization, you can also use serialization libraries that offer Kotlin-support, like Jackson or Moshi. We will go into more detail on the implementation of the Kotlinx Serialization.
Setup the Project
We’re not going to go into the details of integrating Huawei HMS Core into a project. You can follow the instructions to integrate HMS Core into your project via official docs or codelab. After integrating HMS Core, let’s add the necessary dependencies.
Add the necessary dependencies to build.gradle (app level)
Java:
plugins {
id 'com.huawei.agconnect' // HUAWEI agconnect Gradle plugin'
id 'org.jetbrains.kotlin.plugin.serialization' // Kotlinx Serialization
}
android {
buildFeatures {
// Enable ViewBinding
viewBinding true
}
}
dependencies {
// HMS Network Kit
implementation 'com.huawei.hms:network-embedded:5.0.1.301'
// Kotlinx Serialization
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}
We’ll use viewBinding instead of findViewById. It generates a binding class for each XML layout file present in that module. With the instance of a binding class, we can access the view hierarchy with type and null safety.
We used the kotlinx-servialization-json:1.01 version instead of the latest version 1.1.0 in our project. If you use version 1.1.0 and your Kotlin version is smaller than 1.4.30-M1, you will get an error like this:
Code:
Your current Kotlin version is 1.4.10, while kotlinx.serialization core runtime 1.1.0 requires at least Kotlin 1.4.30-M1.
Therefore, if you want to use the latest version of Kotlinx Serialization, please make sure that your Kotlin version is higher than 1.4.30-M1.
Add the necessary dependencies to build.gradle (project level)
Java:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.huawei.agconnect:agcp:1.4.1.300' // HUAWEI Agcp plugin
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // Kotlinx Serialization
}
}
Declaring Required Network Permissions
To use functions of Network Kit, we need to declare required permissions in the AndroidManifest.xml file.
XML:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Initialize the Network Kit
Let’s create an Application class and initialize the Network Kit here.
Java:
class App : Application() {
private val TAG = "Application"
override fun onCreate() {
super.onCreate()
initNetworkKit()
}
private fun initNetworkKit() {
NetworkKit.init(applicationContext, object : NetworkKit.Callback() {
override fun onResult(result: Boolean) {
if (result) {
Log.i(TAG, "NetworkKit init success")
} else {
Log.i(TAG, "NetworkKit init failed")
}
}
})
}
}
Note: Don’t forget to add the App class to the Android Manifest file.
XML:
<manifest ...>
...
<application
android:name=".App"
...
</application>
</manifest>
ApiClient
getApiClient() -> It returns the RestClient instance as a Singleton. We can set the connection time out value here. Also, we specified the base URL.
Java:
const val BASE_URL = "https://jsonplaceholder.typicode.com/"
class ApiClient {
companion object {
private var restClient: RestClient? = null
fun getApiClient(): RestClient {
val httpClient = HttpClient.Builder()
.callTimeout(1000)
.connectTimeout(10000)
.build()
if (restClient == null) {
restClient = RestClient.Builder()
.baseUrl(BASE_URL)
.httpClient(httpClient)
.build()
}
return restClient!!
}
}
}
ApiInterface
We specified the request type as GET and pass the relative URL as “users”. And, it returns us the results as String.
Java:
interface ApiInterface {
@GET("users")
fun fetchUsers(): Submit<String>
}
User — Model Class
As I mentioned earlier, we get the data as a string. Then, we’ll convert data to User object help of the Kotlinx Serialization library. To perform this process, we have to add some annotations to our data class.
@serializable -> We can make a class serializable by annotating it.
@SerialName() -> The variable name in our data must be the same as we use in the data class. If we want to set different variable names, we should use @SerialName annotation.
Java:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("id")
val Id: Int = 0,
val name: String = "",
val username: String = "",
val email: String = "",
)
UserDiffUtil
To tell the RecyclerView that an item in the list has changed, we’ll use the DiffUtil instead of the notifyDataSetChanged().
DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. And, it uses The Myers Difference Algorithm to do this calculation.
What makes notifyDataSetChanged() inefficient is that it forces to recreate all visible views as opposed to just the items that have changed. So, it is an expensive operation.
Java:
class UserDiffUtil(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].Id == newList[newItemPosition].Id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
}
row_user.xml
We have two TextView to show userId and the userName. We’ll use this layout in the RecylerView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tv_userId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<View
android:id="@+id/divider_vertical"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_userId"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_userName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/divider_vertical"
app:layout_constraintTop_toTopOf="parent"
tools:text="Antonio Vivaldi" />
<View
android:id="@+id/divider_horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
UserAdapter
It contains the adapter and the ViewHolder class.
Java:
class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
private var oldUserList = emptyList<User>()
class UserViewHolder(val binding: RowUserBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder(
RowUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.binding.tvUserId.text = oldUserList[position].Id.toString()
holder.binding.tvUserName.text = oldUserList[position].name
}
override fun getItemCount(): Int = oldUserList.size
fun setData(newUserList: List<User>) {
val diffUtil = UserDiffUtil(oldUserList, newUserList)
val diffResults = DiffUtil.calculateDiff(diffUtil)
oldUserList = newUserList
diffResults.dispatchUpdatesTo(this)
}
}
activity_main.xml
It contains only a recyclerview to show the user list.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
userAdapter - We create a adapter for the RecyclerView.
apiClient - We create a request API object using the RestClient object (ApiClient).
Network Kit provides two ways to send network request: synchronous and asynchronous.
Synchronous requests block the client until the operation completes. We can only get data after it finishes its task.
An asynchronous request doesn’t block the client and we can receive a callback when the data has been received.
getUsersAsSynchronous() - We use synchronous requests here. Firstly, we get the response from RestApi. Then, we need to convert the JSON data to User objects. We use the decodeFromString function to do this. Also, we set ignoreUnknownKeys = true, because we don’t use all user information inside the JSON file. We just get the id, name, username, and email. If you don’t put all information inside your Model Class (User), you have to set this parameter as true. Otherwise, you will get an error like:
Code:
Use ‘ignoreUnknownKeys = true’ in ‘Json {}’ builder to ignore unknown keys.
We call this function inside the onCreate. But, we are in the main thread, and we cannot call this function directly from the main thread. If we try to do this, it will crash and give an error like:
Code:
Caused by: android.os.NetworkOnMainThreadException
We should change our thread. So, we call getUsersAsSynchronous() function inside the tread. Then, we get the data successfully. But, there is still one problem. We changed our thread and we cannot change any view without switching to the main thread. If we try to change a view before switching the main thread, it will give an error:
Code:
D/MainActivity: onFailure: Only the original thread that created a view hierarchy can touch its views.
So, we use the runOnUiThread function to run our code in the main thread. Finally, we send our data to the recyclerview adapter to show on the screen as a list.
getUsersAsAsynchronous() - We use asynchronous requests here. We send a network request and wait for the response without blocking the thread. When we get the response, we can show the user list on the screen. Also, we don’t need to call our asynchronous function inside a different thread. But, if we want to use any view, we should switch to the main thread. So, we use the runOnUiThread function to run our code in the main thread again.
Java:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val TAG = "MainActivity"
private val userAdapter by lazy { UserAdapter() }
private val apiClient by lazy {
ApiClient.getApiClient().create(ApiInterface::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager([email protected])
adapter = userAdapter
}
getUsersAsAsynchronous()
/*
thread(start = true) {
getUsersAsSynchronous()
}
*/
}
private fun getUsersAsSynchronous() {
val response = apiClient.fetchUsers().execute()
if (response.isSuccessful) {
val userList =
Json { ignoreUnknownKeys = true }.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
private fun getUsersAsAsynchronous() {
apiClient.fetchUsers().enqueue(object : Callback<String>() {
override fun onResponse(p0: Submit<String>?, response: Response<String>?) {
if (response?.isSuccessful == true) {
val userList = Json {
ignoreUnknownKeys = true
}.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
override fun onFailure(p0: Submit<String>?, p1: Throwable?) {
Log.d(TAG, "onFailure: ${p1?.message.toString()}")
}
})
}
}
Tips & Tricks
You can use Coroutines to manage your thread operations and perform your asynchronous operations easily.
You can use Sealed Result Class to handle the network response result based on whether it was a success or failure.
Before sending network requests, you can check that you’re connected to the internet using the ConnectivityManager.
Conclusion
In this article, we have learned how to use Network Kit in your network operations. And, we’ve developed a sample app that lists user information obtained from the REST Server. In addition to sending requests using either an HttpClient object or a RestClient object, Network Kit offers file upload and download featuring. Please do not hesitate to ask your questions as a comment.
Thank you for your time and dedication. I hope it was helpful. See you in other articles.
References
Huawei Network Kit Official Documentation
Huawei Network Kit Official Codelab
Click to expand...
Click to collapse
Can we get All network Information?
ProManojKumar said:
which permission are required?
Click to expand...
Click to collapse
Hello, Network Kit requires the following permission:
XML:
<!--To obtain the network status-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<!--To access the Internet-->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--To obtain the Wi-Fi status-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
But, If you want to use the upload and download functions of the Network Kit, you should also add the storage permissions:
XML:
<!--To read data from the memory on user devices-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<!--To write data to the memory on user devices-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
Does it support Flutter?
Basavaraj.navi said:
Does it support Flutter?
Click to expand...
Click to collapse
Hi, Flutter doesn't support Flutter yet.

Network Operation in Android with Huawei Network Kit

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
Hi everyone, In this article, we’ll take a look at the Huawei Network Kit and how to use it with Rest APIs. Then, we will develop a demo app using Kotlin in the Android Studio. Finally, we’ll talk about the most common types of errors when making network operations on Android and how you can avoid them.
Huawei Network Kit
Network Kit is a service suite that allows us to perform our network operations quickly and safely. It provides a powerful interacting with Rest APIs and sending synchronous and asynchronous network requests with annotated parameters. Also, it allows us to quickly and easily upload or download files with additional features such as multitasking, multithreading, resumable uploads, and downloads. Lastly, we can use it with other Huawei kits such as hQUIC Kit and Wireless Kit to get faster network traffic.
Our Sample Project
In this application, we'll get a user list from a Rest Service and show the user information on the list. When we are developing the app, we'll use these libraries:
RecyclerView
DiffUtil
Kotlinx Serialization
ViewBinding
To make it simple, we don't use an application architecture like MVVM and a progress bar to show the loading status of the data.
The file structure of our sample app:
Website for Rest API
JsonPlaceHolder is a free online Rest API that we can use whenever we need some fake data. We’ll use the fake user data from the below link. And, it gives us the user list as Json, click Here.
Why we are going to use Kotlin Serialization instead of Gson ?
Firstly, we need a serialization library to convert JSON data to objects in our app. Gson is a very popular library for serializing and deserializing Java objects and JSON. But, we are using the Kotlin language and Gson is not suitable for Kotlin. Because Gson doesn’t respect non-null types in Kotlin.
If we try to parse such as a string with GSON, we’ll find out that it doesn’t know anything about Kotlin default values, so we’ll get the NullPointerExceptions as an error. Instead of Kotlinx Serialization, you can also use serialization libraries that offer Kotlin-support, like Jackson or Moshi. We will go into more detail on the implementation of the Kotlinx Serialization.
Setup the Project
We are not going to go into the details of integrating Huawei HMS Core into a project. You can follow the instructions to integrate HMS Core into your project via official docs or codelab. After integrating HMS Core, let’s add the necessary dependencies.
Add the necessary dependencies to build.gradle (app level).
Code:
plugins {
id 'com.huawei.agconnect' // HUAWEI agconnect Gradle plugin'
id 'org.jetbrains.kotlin.plugin.serialization' // Kotlinx Serialization
}
android {
buildFeatures {
// Enable ViewBinding
viewBinding true
}
}
dependencies {
// HMS Network Kit
implementation 'com.huawei.hms:network-embedded:5.0.1.301'
// Kotlinx Serialization
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}
We’ll use viewBinding instead of findViewById. It generates a binding class for each XML layout file present in that module. With the instance of a binding class, we can access the view hierarchy with type and null safety.
We used the kotlinx-servialization-json:1.01 version instead of the latest version 1.1.0 in our project. If you use version 1.1.0 and your Kotlin version is smaller than 1.4.30-M1, you will get an error like this:
Code:
Your current Kotlin version is 1.4.10, while kotlinx.serialization core runtime 1.1.0 requires at least Kotlin 1.4.30-M1.
Therefore, if you want to use the latest version of Kotlinx Serialization, please make sure that your Kotlin version is higher than 1.4.30-M1.
Add the necessary dependencies to build.gradle (project level)
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.huawei.agconnect:agcp:1.4.1.300' // HUAWEI Agcp plugin
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // Kotlinx Serialization
}
}
Declaring Required Network Permissions
To use functions of Network Kit, we need to declare required permissions in the AndroidManifest.xml file.
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Initialize the Network Kit
Let’s create an Application class and initialize the Network Kit here.
Code:
class App : Application() {
private val TAG = "Application"
override fun onCreate() {
super.onCreate()
initNetworkKit()
}
private fun initNetworkKit() {
NetworkKit.init(applicationContext, object : NetworkKit.Callback() {
override fun onResult(result: Boolean) {
if (result) {
Log.i(TAG, "NetworkKit init success")
} else {
Log.i(TAG, "NetworkKit init failed")
}
}
})
}
}
Note: Don’t forget to add the App class to the Android Manifest file.
Code:
<manifest ...>
...
<application
android:name=".App"
...
</application>
</manifest>
ApiClient
getApiClient(): It returns the RestClient instance as a Singleton. We can set the connection time out value here. Also, we specified the base URL.
Code:
const val BASE_URL = "https://jsonplaceholder.typicode.com/"
class ApiClient {
companion object {
private var restClient: RestClient? = null
fun getApiClient(): RestClient {
val httpClient = HttpClient.Builder()
.callTimeout(1000)
.connectTimeout(10000)
.build()
if (restClient == null) {
restClient = RestClient.Builder()
.baseUrl(BASE_URL)
.httpClient(httpClient)
.build()
}
return restClient!!
}
}
}
ApiInterface
We specified the request type as GET and pass the relative URL as “users”. And, it returns us the results as String.
Code:
interface ApiInterface {
@GET("users")
fun fetchUsers(): Submit<String>
}
User  - Model Class
As I mentioned earlier, we get the data as a string. Then, we’ll convert data to User object help of the Kotlinx Serialization library. To perform this process, we have to add some annotations to our data class.
@serializable -> We can make a class serializable by annotating it.
@SerialName() -> The variable name in our data must be the same as we use in the data class. If we want to set different variable names, we should use @SerialName annotation.
Code:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("id")
val Id: Int = 0,
val name: String = "",
val username: String = "",
val email: String = "",
)
UserDiffUtil
To tell the RecyclerView that an item in the list has changed, we’ll use the DiffUtil instead of the notifyDataSetChanged().
DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. And, it uses The Myers Difference Algorithm to do this calculation.
What makes notifyDataSetChanged() inefficient is that it forces to recreate all visible views as opposed to just the items that have changed. So, it is an expensive operation.
Code:
class UserDiffUtil(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].Id == newList[newItemPosition].Id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
}
row_user.xml
We have two TextView to show userId and the userName. We’ll use this layout in the RecylerView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tv_userId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<View
android:id="@+id/divider_vertical"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_userId"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_userName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/divider_vertical"
app:layout_constraintTop_toTopOf="parent"
tools:text="Antonio Vivaldi" />
<View
android:id="@+id/divider_horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
UserAdapter
It contains the adapter and the ViewHolder class.
Code:
class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
private var oldUserList = emptyList<User>()
class UserViewHolder(val binding: RowUserBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder(
RowUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.binding.tvUserId.text = oldUserList[position].Id.toString()
holder.binding.tvUserName.text = oldUserList[position].name
}
override fun getItemCount(): Int = oldUserList.size
fun setData(newUserList: List<User>) {
val diffUtil = UserDiffUtil(oldUserList, newUserList)
val diffResults = DiffUtil.calculateDiff(diffUtil)
oldUserList = newUserList
diffResults.dispatchUpdatesTo(this)
}
}
activity_main.xml
It contains only a recyclerview to show the user list.
Code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
userAdapter: We create a adapter for the RecyclerView.
apiClient: We create a request API object using the RestClient object (ApiClient).
Network Kit provides two ways to send network request: synchronous and asynchronous.
Synchronous requests block the client until the operation completes. We can only get data after it finishes its task.
An asynchronous request doesn’t block the client and we can receive a callback when the data has been received.
getUsersAsSynchronous(): We use synchronous requests here. Firstly, we get the response from RestApi. Then, we need to convert the JSON data to User objects. We use the decodeFromString function to do this. Also, we set ignoreUnknownKeys = true, because we don’t use all user information inside the JSON file. We just get the id, name, username, and email. If you don’t put all information inside your Model Class (User), you have to set this parameter as true. Otherwise, you will get an error like:
Code:
Use ‘ignoreUnknownKeys = true’ in ‘Json {}’ builder to ignore unknown keys.
We call this function inside the onCreate. But, we are in the main thread, and we cannot call this function directly from the main thread. If we try to do this, it will crash and give an error like:
Code:
Caused by: android.os.NetworkOnMainThreadException
We should change our thread. So, we call getUsersAsSynchronous() function inside the tread. Then, we get the data successfully. But, there is still one problem. We changed our thread and we cannot change any view without switching to the main thread. If we try to change a view before switching the main thread, it will give an error:
Code:
D/MainActivity: onFailure: Only the original thread that created a view hierarchy can touch its views.
So, we use the runOnUiThread function to run our code in the main thread. Finally, we send our data to the recyclerview adapter to show on the screen as a list.
getUsersAsAsynchronous() - We use asynchronous requests here. We send a network request and wait for the response without blocking the thread. When we get the response, we can show the user list on the screen. Also, we don’t need to call our asynchronous function inside a different thread. But, if we want to use any view, we should switch to the main thread. So, we use the runOnUiThread function to run our code in the main thread again.
Code:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val TAG = "MainActivity"
private val userAdapter by lazy { UserAdapter() }
private val apiClient by lazy {
ApiClient.getApiClient().create(ApiInterface::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager([email protected])
adapter = userAdapter
}
getUsersAsAsynchronous()
/*
thread(start = true) {
getUsersAsSynchronous()
}
*/
}
private fun getUsersAsSynchronous() {
val response = apiClient.fetchUsers().execute()
if (response.isSuccessful) {
val userList =
Json { ignoreUnknownKeys = true }.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
private fun getUsersAsAsynchronous() {
apiClient.fetchUsers().enqueue(object : Callback<String>() {
override fun onResponse(p0: Submit<String>?, response: Response<String>?) {
if (response?.isSuccessful == true) {
val userList = Json {
ignoreUnknownKeys = true
}.decodeFromString<List<User>>(response.body)
runOnUiThread {
userAdapter.setData(userList)
}
}
}
override fun onFailure(p0: Submit<String>?, p1: Throwable?) {
Log.d(TAG, "onFailure: ${p1?.message.toString()}")
}
})
}
}
Tips and Tricks
You can use Coroutines to manage your thread operations and perform your asynchronous operations easily.
You can use Sealed Result Class to handle the network response result based on whether it was a success or failure.
Before sending network requests, you can check that you’re connected to the internet using the ConnectivityManager.
Conclusion
In this article, we have learned how to use Network Kit in your network operations. And, we’ve developed a sample app that lists user information obtained from the REST Server. In addition to sending requests using either an HttpClient object or a RestClient object, Network Kit offers file upload and download featuring. Please do not hesitate to ask your questions as a comment.
Thank you for your time and dedication. I hope it was helpful. See you in other articles.
References
Huawei Network Kit Official Documentation
Huawei Network Kit Official Codelab
Original Source
Is it available for cross platform(Xamarin)?

Categories

Resources