Introduction
Huawei offer a range of ad formats so you can choose whichever that suits your app best. Currently, you can integrate Banner, Native, Rewarded, Interstitial, Splash, and Roll ads, and we will be launching even more formats in the future.
Use the HUAWEI Ads SDK to quickly integrate HUAWEI Ads into your app.
Native Ads
Native ads fit seamlessly into the surrounding content to match your app design. Such ads can be customized as needed.
{
"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"
}
In this article we can learn about how to integrate Native image and video Ads in between RecyclerView items.
Check the below activity_main.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<RelativeLayout
android:id="@+id/rl_tool"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Music Player"
android:textColor="#fff"
android:textSize="16sp" />
</RelativeLayout>
<TextView
android:id="@+id/txt_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/rl_tool"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="Playlist"
android:textColor="#fff"
android:textSize="16sp" />
<com.yarolegovich.discretescrollview.DiscreteScrollView
android:id="@+id/discrete_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/hw_banner_view"
android:layout_below="@id/txt_category"
app:dsv_orientation="vertical" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="false"
android:indeterminateDrawable="@drawable/circular_progress"
android:visibility="invisible" />
<com.huawei.hms.ads.banner.BannerView
android:id="@+id/hw_banner_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
After that check below MainActivity for how to get API response data by using Volley library
Code:
public class MainActivity extends AppCompatActivity {
private DiscreteScrollView discreteScrollView;
public static String BASE_URL = "https://beatsapi.media.jio.com/v2_1/beats-api/jio/src/response/home/";
private String BASE_IMAGE_URL;
private ArrayList<PlaylistModel> playlist = new ArrayList();
private boolean isConnected = false;
private HomeAdapterNew homeAdapter;
private SharedPreferences preferences;
private SharedPreferences.Editor editor;
private ProgressBar progressBar;
private BannerView bannerView;
private RelativeLayout rlRoot;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
addBannerAd();
initHomeAdapter();
if (NetworkUtil.isNetworkConnected(this))
callHomeResponseApiVolley();
else {
String homeResponse = preferences.getString("HomeResponse", "");
filterResponse(homeResponse);
}
}
private void init() {
preferences = this.getSharedPreferences("MyPref", Context.MODE_PRIVATE);
discreteScrollView = findViewById(R.id.discrete_scroll_view);
progressBar = findViewById(R.id.progress_bar);
bannerView = findViewById(R.id.hw_banner_view);
rlRoot = findViewById(R.id.rl_root);
}
private void initHomeAdapter() {
homeAdapter = new HomeAdapterNew(this);
discreteScrollView.setAdapter(homeAdapter);
discreteScrollView.setSlideOnFling(true);
discreteScrollView.setItemTransformer(new ScaleTransformer.Builder()
.setMaxScale(1.05f)
.setMinScale(0.8f)
.setPivotX(Pivot.X.CENTER)
.setPivotY(Pivot.Y.CENTER)
.build());
discreteScrollView.addScrollStateChangeListener(new DiscreteScrollView.ScrollStateChangeListener<RecyclerView.ViewHolder>() {
@Override
public void onScrollStart(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
}
@Override
public void onScrollEnd(@NonNull RecyclerView.ViewHolder viewHolder, int adapterPosition) {
}
@Override
public void onScroll(float v, int i, int i1, @Nullable RecyclerView.ViewHolder viewHolder, @Nullable RecyclerView.ViewHolder t1) {
}
});
}
private void addBannerAd() {
bannerView.setAdId("testw6vs28auh3");
bannerView.setBannerAdSize(BannerAdSize.BANNER_SIZE_360_57);
AdParam adParam = new AdParam.Builder().build();
bannerView.loadAd(adParam);
}
private void callHomeResponseApiVolley() {
progressBar.setVisibility(View.VISIBLE);
StringRequest request = new StringRequest(Request.Method.GET, BASE_URL + "Telugu", new com.android.volley.Response.Listener<String>() {
@Override
public void onResponse(String response) {
progressBar.setVisibility(View.INVISIBLE);
if (response != null) {
editor = preferences.edit();
editor.putString("HomeResponse", response);
editor.apply();
filterResponse(response);
}
}
}, new com.android.volley.Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
progressBar.setVisibility(View.INVISIBLE);
}
});
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(request);
}
void filterResponse(String response) {
try {
JSONObject object = new JSONObject(response);
JSONObject resultObject = object.getJSONObject("result");
JSONArray jsonDataArray = resultObject.getJSONArray("data");
BASE_IMAGE_URL = resultObject.getString("imageurl");
Log.e("BASE_URL_IMAGE", BASE_IMAGE_URL);
for (int i = 0; i < jsonDataArray.length(); i++) {
String type = jsonDataArray.getJSONObject(i).getString("type");
JSONArray songsListArray = jsonDataArray.getJSONObject(i).getJSONArray("list");
if (type.equalsIgnoreCase("playlist")) {
getSongsFromArray(songsListArray);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void getSongsFromArray(JSONArray songsListArray) throws JSONException {
for (int i = 0; i < songsListArray.length(); i++) {
JSONObject jsonObject = songsListArray.getJSONObject(i);
String title = jsonObject.getString("title");
String imageUrl = jsonObject.getString("image");
Log.e("ImageUrl", imageUrl);
String playlistID = jsonObject.getString("playlistid");
playlist.add(new PlaylistModel(title, playlistID, BASE_IMAGE_URL + imageUrl));
}
ArrayList<AdsListModel> adsList = new ArrayList<>();
adsList.add(new AdsListModel(getString(R.string.ad_id_native_image)));
adsList.add(new AdsListModel(getString(R.string.ad_id_native_video)));
homeAdapter.addList(playlist, adsList);
}
}
Banner Ads
Banner ads are rectangular images that occupy a spot at the top, middle, or bottom within an app layout. Banner ads refresh automatically at regular intervals. When a user clicks a banner ad, the user is usually redirected to the advertiser page.
Here in addBannerAd method bannerView taken from xml. We can integrate programatically. Check the below code
Code:
BannerView bannerView = new BannerView(this);
bannerView.setAdId("testw6vs28auh3");
bannerView.setBannerAdSize(BannerAdSize.BANNER_SIZE_SMART);
AdParam adParam = new AdParam.Builder().build();
bannerView.loadAd(adParam);
RelativeLayout.LayoutParams rLParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rLParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1);
rlRoot.addView(bannerView, rLParams);
Standard Banner Ad Sizes
The following table lists the standard banner ad sizes.
NOTE :
In the Chinese mainland, only BANNER_SIZE_360_57 and BANNER_SIZE_360_144 are supported.
In the above getSongsFromArray method, We are passing both playlist data and ads data to RecyclerView adapter
with the help of addList method in HomeAdapter
Check below for Native Ad slot id
Code:
<string name="ad_id_native_image">testu7m3hc4gvm</string>
<string name="ad_id_native_video">testy63txaom86</string>
Check the below PlaylistModel class for playlist data
Code:
public class PlaylistModel extends BaseListModel implements Serializable {
String playlistTitle;
String playlistID;
String playlistImage;
public PlaylistModel(String title, String playlistID, String playlistImage) {
this.playlistID = playlistID;
this.playlistImage = playlistImage;
this.playlistTitle = title;
}
public String getPlaylistID() {
return playlistID;
}
public String getPlaylistImage() {
return playlistImage;
}
public String getPlaylistTitle() {
return playlistTitle;
}
}
Check the below code for AdsListModel
Code:
public class AdsListModel extends BaseListModel {
private String adID;
AdsListModel(String ID) {
this.adID = ID;
}
public String getAdID() {
return adID;
}
}
Check the below code for BaseListModel
Code:
public class BaseListModel {
}
After that for showing content item and ad item, Need to create two different layouts. One for actual content and another for ad.
Check the below inflate_home_item.xml for showing actual content
Code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="300dp"
android:layout_height="350dp"
android:layout_margin="10dp"
app:cardBackgroundColor="#000"
app:cardCornerRadius="5dp"
app:cardElevation="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_playlist_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="20dp"
android:singleLine="true"
android:text="Prabhas Playlist"
android:textColor="#fff"
android:textSize="16sp" />
<ImageView
android:id="@+id/img_home"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_below="@id/txt_playlist_name"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:scaleType="fitXY"
android:transitionName="PlaylistImage" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
More details, you can check https://forums.developer.huawei.com/forumPortal/en/topic/0204417759943370014
Very useful.
Really interesting. Thanks.
Quite helpful.
Quite interesting.
How ad layout behaves when device orientation is changed?
Related
More information like this, you can visit HUAWEI Developer Forum
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201345264040690134&fid=0101187876626530001
Overview
This article covers how to add custom marker, custom information window and custom layout as Marker to Maps.
1. How to add Custom Marker to Maps.
2. How to add Custom Info Window to Marker.
3. How to add Custom Layout as A Marker.
1. How to add Custom Marker to Maps.
{
"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"
}
Use below method to add Custom Marker.
Code:
private void addCustomMarker(LatLng latLng) {
if (customMarker != null) {
customMarker .remove();
}
customMarker = huaweiMap.addMarker(new MarkerOptions()
.position(latLng)
.icon(BitmapDescriptorFactory.fromBitmap(getCustomBitmap(R.drawable.custom_marker_image)))
.title("CUSTOM_MARKER"));
}
Code:
BitmapDescriptorFactory.fromBitmap(getCustomBitmap(R.drawable.custom_marker_image))
In the above we are giving bitmap object to the BitmapDescriptorFactory.fromBitmap() method.
Use below method to convert drawable image resource to Bitmap Image.
Code:
private Bitmap getCustomBitmap(int imageResource) {
int height = (int) getResources().getDimension(R.dimen.dimen_40);
int width = (int) getResources().getDimension(R.dimen.dimen_40);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imageResource);
return Bitmap.createScaledBitmap(bitmap, width, height, false);
}
Find the output in below image.
2. How to add Custom Info Window to Marker.
To add custom info window, first we need to create required design in xml based on our requirements below is the xml code.
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000"
android:padding="5dp">
<ImageView
android:id="@+id/img_place"
android:layout_width="@dimen/dimen_50"
android:visibility="gone"
android:layout_height="@dimen/dimen_50"
android:src="@drawable/cinema_holder" />
<TextView
android:id="@+id/txt_place_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/img_place"
android:text="Hotel Charminar"
android:textColor="#fff"
android:textSize="12sp"
android:textStyle="bold" />
</RelativeLayout>
<View
android:layout_width="2dp"
android:layout_height="@dimen/dimen_30"
android:layout_gravity="center"
android:background="#000" />
</LinearLayout>
After that create Java class and implement HuaweiMap.InfoWindowAdapter, it will override getInfoContents and getInfoWindow methods.
In getInfoContents method we will get Marker object based on marker title we can inflate different layouts for Info window. See the below code
Code:
public class CustomInfoAdapter implements HuaweiMap.InfoWindowAdapter {
Context context;
public CustomInfoAdapter(Context context) {
this.context = context;
}
@Override
public View getInfoContents(Marker marker) {
View view = null;
if (marker.getTitle().equals("PLACES_MARKER")) {
view = LayoutInflater.from(context).inflate(R.layout.inflate_custom_places, null);
TextView txtPlaceName = view.findViewById(R.id.txt_place_name);
String placeName = (String) marker.getTag();
txtPlaceName.setText(placeName);
return view;
} else if (marker.getTitle().equals("MY_MARKER")) {
view = LayoutInflater.from(context).inflate(R.layout.layout_custom_window, null);
TextView txtName = view.findViewById(R.id.txt_name);
txtName.setText("You are here");
return view;
} else if (marker.getTitle().equals("CUSTOM_MARKER")) {
view = LayoutInflater.from(context).inflate(R.layout.custom_info_window_selected, null);
TextView txtPlaceName = view.findViewById(R.id.txt_place_name);
String placeName = (String) marker.getTag();
txtPlaceName.setText(placeName);
return view;
}
return null;
}
@Override
public View getInfoWindow(Marker marker) {
return null;
}
}
That's it everything done now just set CustomInfoAdapter class object to huaweiMap.setInfoWindowAdapter() method onMapReady callback. See the below code
Code:
@Override
public void onMapReady(HuaweiMap map) {
huaweiMap = map;
huaweiMap.setInfoWindowAdapter(new CustomInfoAdapter(this));
huaweiMap.setOnMarkerClickListener(new HuaweiMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
marker.showInfoWindow();
return true;
}
});
}
Find the output in below image.
3. How to add Custom Layout as A Marker.
To add custom layout as Marker, create required design in xml based on our requirements below is the xml code.
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_marker"
android:layout_width="@dimen/dimen_40"
android:layout_height="@dimen/dimen_40"
android:scaleType="fitXY"
android:src="@drawable/custom_marker" />
<TextView
android:id="@+id/txt_place_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/img_marker"
android:layout_marginStart="@dimen/dimen_50"
android:layout_marginTop="-10dp"
android:text="Renigunta Railway Station"
android:textColor="@color/red"
android:textSize="14sp"
android:textStyle="bold" />
</RelativeLayout>
After creating xml inflate layout and set required data as below.
Code:
private void addLayoutAsMarker(Coordinate location,String placeName) {
if (location == null) {
return;
}
if (selectedMarker != null)
selectedMarker.remove();
View view = LayoutInflater.from(this).inflate(R.layout.inflate_content_marker, null, false);
TextView txtPlaceName = view.findViewById(R.id.txt_place_name);
txtPlaceName.setText(placeName);
LatLng latLng = new LatLng(location.getLat(), location.getLng());
selectedMarker = huaweiMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromBitmap(loadBitmapFromView(view))));
huaweiMap.setInfoWindowAdapter(new CustomInfoAdapter(this));
selectedMarker.setTitle("CUSTOM_MARKER");
selectedMarker.setTag(placeName);
}
Here before setting bitmap to BitmapDescriptorFactory.fromBitmap() method. We need to convert inflated view as Bitmap, see the below code
Code:
public Bitmap loadBitmapFromView(View view) {
if (view.getMeasuredHeight() <= 0) {
view.measure(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.draw(canvas);
return bitmap;
}
return null;
}
Setting Custom Marker: Convert drawable to Bitmap
Setting Custom Layout as Marker: Convert view to Bitmap
Find the output in below image.
In the above xml layout added one ImageView and TextView as example, so we can add more based on your requirements.
Conclusion:
In this article added custom markers and custom info windows, Use this as reference make rich Map user interface
Reference link:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-drawing-on-map-0000001050158671
In this article I will explain how to develop a basic messaging app by Huawei Push Kit. If you want to develop a messaging app or add messaging section into your application, I hope this article will be useful for you.
Before beginning, this application and article will be addition of my first ones. If you have not seen that article, you can see the link below. It also shows how to integrate Push Kit into your Xamarin.Android Application.
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201314061860830181&fid=0101187876626530001
Layout of activity_main
We will only add our messaging button to pass to messaging section. Final version of “activity_main.xml” will be like this.
Code:
<LinearLayout
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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<Button
android:text="Get Token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:id="@+id/btnGetToken" />
<Button
android:text="Send Test Notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnNotification" />
<Button
android:text="Send Test Data Message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnDataMessage" />
<Button
android:text="Messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnMessagingMain" />
</LinearLayout>
MainActivity Class
Firstly, we will inherit ViewAnimator.IOnClickListener interface to this class. Then, we will organize inside of OnCreate Method and implement OnClick method.
Code:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
//Identifying Buttons
btnGetToken = FindViewById<Button>(Resource.Id.btnGetToken);
btnNotification = FindViewById<Button>(Resource.Id.btnNotification);
btnDataMessage = FindViewById<Button>(Resource.Id.btnDataMessage);
btnMessagingMain = FindViewById<Button>(Resource.Id.btnMessagingMain);
//Set onClick listener to buttons
btnGetToken.SetOnClickListener(this);
btnNotification.SetOnClickListener(this);
btnDataMessage.SetOnClickListener(this);
btnMessagingMain.SetOnClickListener(this);
//Create instance of Broadcast Receiver for data message service
myReceiver = new MyBroadcastReceiver();
CheckPermission(new string[] { Android.Manifest.Permission.Internet,
Android.Manifest.Permission.AccessNetworkState,
Android.Manifest.Permission.WakeLock}, 100);
}
Secondly, we will arrange OnClick method so that we can call these events from one method.
Code:
public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnGetToken:
GetToken();
break;
case Resource.Id.btnNotification:
HmsPushKit.SendNotification(tokenFromGetToken, "Title", "Body");
break;
case Resource.Id.btnDataMessage:
HmsPushKit.SendDataMessage(tokenFromGetToken, "{\"Type\":\"Test\",\"Message\":\"Welcome\"}");
break;
case Resource.Id.btnMessagingMain:
StartMessagingMain();
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}
Thirdly, as you guess we will add StartMessagingMain method to call MessagingMainActivity class. Calling StartActivity method is adequate here. We don’t have to send any Intent.
Finally, we will move AccessToken, SendDataMessage and SendNotification method to new HmsPushKit class.
HmsPushKit.cs
We should be able to call these methods from any other class. Because of that, methods in this class should be static.
Code:
public static class HmsPushKit
{
static readonly string appID = "AppID"; //AppGallery Connect > Project Setting > App information > App ID
static readonly string appKey = "AppKey"; //AppGallery Connect > Project Setting > App information > App key
static readonly HttpClient client = new HttpClient();
public static async Task<string> GetAccessToken()
{
string uri = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
var values = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_id", appID },
{ "client_secret", appKey }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync(uri, content);
var jsonResponse = JObject.Parse(await response.Content.ReadAsStringAsync()); //Install-Package Newtonsoft.Json
string accessToken = jsonResponse["access_token"].ToString(); //It is valid for 3600 seconds
return accessToken;
}
/// <summary>
/// Send notification by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="notTitle">Notification Title</param>
/// <param name="notBody">Notification Body</param>
public static async void SendNotification(string tokenUSendTo,string notTitle,string notBody)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
notification = new
{
title = notTitle,
body = notBody
},
android = new
{
notification = new
{
click_action = new
{
type = 3
}
}
},
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
/// <summary>
/// Send data message by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="serializedObject">Use JsonConvert.SerializeObject function</param>
public static async void SendDataMessage(string tokenUSendTo, string serializedObject)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
data = serializedObject,
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
}
DataBase
Actually, database is not essential for a sample application but I want to make it pretty. I will use sqlite-net-pcl by SQLite-net.
{
"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"
}
This application will need two essential database tables(Model) Person and Message. We will also use UniqueData table for minor data.
Code:
public class Person
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Token { get; set; }
public Person(string Code,string Name)
{
this.Code = Code;
this.Name = Name;
Random rnd = new Random();
Icon = "icon" + rnd.Next(1, 4);
}
public Person()
{
}
}
public class Message
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string CodeFrom { get; set; }
public string CodeTo { get; set; }
public string Text { get; set; }
}
public class UniqueData
{
public string Key { get; set; }
public string Value { get; set; }
}
And the Database class that I use is below. The most part is easy to understand. If you want to use another Model you should add this into CreateDataBase method. Moreover, you don’t have to check if there is a database. If database does not exist, it will create otherwise it will not.
Code:
public class DataBase
{
readonly string dbPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "DB.db");
readonly string _SQLite = "SQLite";
public DataBase()
{
CreateDataBase();
}
public bool CreateDataBase()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.CreateTable<Person>();
connection.CreateTable<Message>();
connection.CreateTable<UniqueDatas>();
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public bool InsertIntoTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Insert(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public List<T> SelectTable<T>() where T:new()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
return connection.Table<T>().ToList();
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return null;
}
}
public bool DeleteFromTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Delete(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
}
Layout of activity_main_messaging
I will share with you a simple screen for the list of people.
My xml file will not contain the rows. We will add them from MessagingMainActivity class. Xml code for this layout below.
Code:
<LinearLayout
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:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/material_grey_300"
android:weightSum="4"
android:paddingHorizontal="2dp">
<Button
android:text="Del"
android:gravity="center_horizontal"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnDel"/>
<TextView
android:text="Chats"
android:gravity="center"
android:textStyle="bold"
android:textColor="@android:color/black"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="4"
android:id="@+id/textView1"
/>
<Button
android:gravity="center_horizontal"
android:text="New"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnNew"/>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout"
android:divider="#B6B6B6"
android:dividerHeight="5px">
</LinearLayout>
</ScrollView>
</LinearLayout>
MessagingMainActivity Class
Things we will do in this class are bringing people from database, adding new person, deleting person and starting messaging. So let’s start with identifing Delete Button, New button and LinearLayout which is in the ScrollView.
After, we will set ClickListener on two buttons therefore, we will inherit ViewAnimator.IOnClickListener to this activity as well.
Later, we need to take instance of our DataBase class then add people into our LinearLayout. Furthermore, I will also add sample data.
At last, we will fill OnClick method.
Code:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main_messaging);
linearLayout = FindViewById<LinearLayout>(Resource.Id.linearLayout);
btnNew = FindViewById<Button>(Resource.Id.btnNew);
btnDel = FindViewById<Button>(Resource.Id.btnDel);
btnNew.SetOnClickListener(this);
btnDel.SetOnClickListener(this);
db = new DataBase();
if (db.SelectTable<Person>().Count == 0)
{
db.InsertIntoTable(new Person { Code = "0", Name = "Enes Durmus", Icon = "icon1" });
db.InsertIntoTable(new Person { Code = "1", Name = "Ahmet Ercek", Icon = "icon2" });
db.InsertIntoTable(new Person { Code = "2", Name = "Gokberk Bardakci", Icon = "icon3" });
}
foreach (Person person in db.SelectTable<Person>())
{
linearLayout.AddView(OneRowLL(this, person));
Log.Info("SQLite", person.ToString());
linearLayout.AddView(GetLine(this, Resources.DisplayMetrics.WidthPixels / 3));// This is just decorative
}
}
public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnNew:
AddPerson();
break;
case Resource.Id.btnDel:
IsDelete = true;
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}
As you see there is OneRowLL method. It returns a linearlayout which contains CircleImageView from Refractored.Controls.CircleImage nuget and linearlayout contains two textviews.
Code:
LinearLayout OneRowLL(Context context, Person person)
{
LinearLayout result = new LinearLayout(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.WrapContent);
result.LayoutParameters = layoutParams;
result.Orientation = Orientation.Horizontal;
result.TransitionName = person.Code;
result.WeightSum = 1;
ViewGroup.LayoutParams vgLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent);
CircleImageView imageView = new CircleImageView(context); // Nuget: Refractored.Controls.CircleImageView
LinearLayout.LayoutParams iLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WrapContent, LinearLayout.LayoutParams.MatchParent) { Weight = 0.6f };
imageView.LayoutParameters = iLayoutParams;
int id = Resources.GetIdentifier("com.companyname.pushdemoforarticle:drawable/" + person.Icon, null, null);
imageView.SetImageResource(id);
imageView.BorderWidth = 6;
imageView.BorderColor = -16777216;
result.AddView(imageView);
LinearLayout subLinearLayout = new LinearLayout(context);
layoutParams.Weight = 1;
subLinearLayout.LayoutParameters = layoutParams;
subLinearLayout.Orientation = Orientation.Vertical;
subLinearLayout.SetPadding(20, 0, 0, 0);
ViewGroup.LayoutParams txtViewLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
subLinearLayout.AddView(new TextView(context)
{
Text = person.Name,
TextSize = 24,
LayoutParameters = txtViewLayoutParams,
Gravity = GravityFlags.Start
});
string _lastMessage = string.Empty;
List<Message> messages = db.SelectTable<Message>().Where(x => x.CodeFrom == person.Code || x.CodeTo == person.Code).ToList();
if (messages.Count != 0)
{
Message lastMessage = messages.TakeLast(1).First();
_lastMessage = lastMessage.CodeFrom == self ? "You: "+lastMessage.Text : lastMessage.Text;
}
subLinearLayout.AddView(new TextView(context)
{
Text = _lastMessage,
LayoutParameters = txtViewLayoutParams,
});
result.AddView(subLinearLayout);
result.Click += LinearLayoutClick;
return result;
}
Afterwards, we will create AddPerson method for btnNew. We will use LayoutInflater so I also create a xml file as well. We need user’s code to get his token and a name to display it.
Code:
private void AddPerson()
{
AlertDialog dialog = InitDialog();
dialog.Show();
}
public AlertDialog InitDialog()
{
//Setting xml from layouts
LayoutInflater layoutInflater = LayoutInflater.From(this);
View view = layoutInflater.Inflate(Resource.Layout.input_box, null);
AlertDialog.Builder alertbuilder = new AlertDialog.Builder(this);
alertbuilder.SetView(view);
//Identifing EditTexts
var userCode = view.FindViewById<EditText>(Resource.Id.txtUserCode);
var userName = view.FindViewById<EditText>(Resource.Id.txtUserName);
//Setting Positive and Negative Buttons
alertbuilder.SetCancelable(false)
.SetPositiveButton("OK", delegate
{
Person person = new Person(userCode.Text.Trim(), userName.Text);
if (db.InsertIntoTable(person))
linearLayout.AddView(OneRowLL(this, person));
}).SetNegativeButton("Cancel", delegate { });
AlertDialog dialog = alertbuilder.Create();
return dialog;
}
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Your Friend"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/txtUserCode"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Code" />
<EditText
android:id="@+id/txtUserName"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Name" />
</LinearLayout>
Next step is btnDel. Delete button will be simple. Click delete then click person afterwards person is gone. That’s adequate for now but you can improve it by adding invisible red delete button to each row and ask for approve.
Finally, LinearLayoutClick for pass to messaging. We will send their code by Intent.
Code:
public void LinearLayoutClick(object sender, EventArgs e)
{
LinearLayout row = sender as LinearLayout;
if (!IsDelete)
{
Intent intent = new Intent(this, typeof(MessagingActivity));
intent.PutExtra("code", row.TransitionName);
StartActivity(intent);
}
else
{
linearLayout.RemoveView(row);
IsDelete = false;
}
}
Layout of activity_messaging.xml
There will be 3 sections. First one is the LinearLayout which shows who we message. Second one is ScrollView for messages. And the last one is EditText for typing and sending. Xml file below.
Code:
<LinearLayout
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:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="100"
android:id="@+id/ll0"
android:transitionName="0"
android:paddingLeft="@dimen/abc_select_dialog_padding_start_material">
<refractored.controls.CircleImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="60"
app:civ_border_width="2dp"
app:civ_border_color="#000000"
android:id="@+id/imageIcon"/>
<LinearLayout
android:paddingLeft="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="100">
<TextView
android:textSize="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:id="@+id/txtName"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtStatus"/>
</LinearLayout>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="100"
android:gravity="bottom"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout" />
</ScrollView>
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="20"
android:id="@+id/textInput"
android:hint="Type here!"
android:layout_marginBottom="4dp"/>
</LinearLayout>
For more information, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202327073597950015&fid=0101187876626530001
Overview
This application helps us for getting the direction from current location to the selected place. This app uses Huawei Site Kit, Location Kit, Map kit and Huawei Direction API for showing the direction. Let us see the uses of all kits in this application.
Site Kit: This kit is used for getting the places and near-by places on keyword search.
Location Kit: This kit is used for getting the current location of the user.
Map Kit: This kit is used for showing the map, adding a marker and drawing polyline on the Huawei Map.
Direction API: This API is used for getting the path, steps and polyline between two places.
Let us start with the project configuration part:
Step 1: Create an app on App Gallery Connect.
Step 2: Enable the Site Kit, Location Lit and Map Kit in Manage APIs menu.
{
"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"
}
Step 3: Create an Android Project with the same package name as App Gallery project package name.
Step 4: Enter the below maven url inside the repositories of buildscript and allprojects (project build.gradle file).
Java:
maven { url ‘http://developer.huawei.com/repo/’ }
Step 5: Add classpath to project’s build.gradle file.
Java:
dependencies {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.huawei.agconnect:agcp:1.3.1.300'
}
Step 6: Apply plugin in App’s build.gradle file at top after application plugin.
Java:
apply plugin: 'com.huawei.agconnect'
Step 7: Add below dependencies to app’s build.gradle file.
Java:
implementation 'com.huawei.hms:site:5.0.2.300'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.cardview:cardview:1.0.0"
implementation 'com.huawei.hms:maps:4.0.0.302'
implementation 'com.huawei.hms:location:4.0.1.300'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.google.code.gson:gson:2.6.1'
Step 8: Add the app ID generated when the creating the app on HUAWEI Developers to manifest file.
Java:
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="appid=your app id" />
Step 9: Add the below permissions to manifest file.
Java:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA"/>
<!-- Allow the app to obtain the coarse longitude and latitude of a user through the Wi-Fi network or base station. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Allow the app to receive location information from satellites through the GPS chip. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Step 10: Generate SHA 256 key and add to App Gallery Connect Project.
Step 11: download the agconnect-services.json from App Information Section. Copy and paste the Json file in the app folder of the android project.
Step 12: Sync the project.
Let us start with the implementation part:
Part 1: Site Kit Integration
Using the Site Kit, we will search for place and get the latitude and longitude.
Step 1: Get the API_KEY from App Gallery and define the same in your MainActivity.Java.
Java:
public static final String MY_API_KEY = "Your API_KEY will come here";
Step 2: Declare a SearchService object and use SearchServiceFactory to initialize the object.
Java:
// Declare SearchService object
private SearchService searchService;
// Initialize the SearchService object
searchService = SearchServiceFactory.create(this, URLEncoder.encode(MY_API_KEY, "utf-8"));
Step 3: create the layout for search a place.
Java:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:gravity="center"
android:paddingLeft="5dp"
android:text="Find your place"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query: "
android:visibility="gone" />
<EditText
android:id="@+id/edit_text_text_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:autofillHints=""
android:background="@drawable/search_bg"
android:hint="Search here "
android:imeOptions="actionGo"
android:inputType="text"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingBottom="5dp"
android:visibility="visible"/>
</LinearLayout>
<Button
android:id="@+id/button_text_search"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:background="@drawable/search_btn_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Search"
android:textAllCaps="false"
android:textColor="@color/upsdk_white" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:paddingLeft="5dp"
android:text="Note: Get site Id suing Keyword/Nearby/Place suggestion search"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
android:padding="10dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:background="#D3D3D3"
android:gravity="center_vertical"
android:paddingLeft="5dp"
android:text="Result"
android:textSize="16sp"
android:visibility="gone" />
<TextView
android:id="@+id/response_text_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textIsSelectable="true"
android:padding="10dp"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchResultList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:visibility="visible"/>
</LinearLayout>
</LinearLayout>
Step 4: Create the ListAdapter for showing the data.
Java:
public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.SearchViewHolder> {
List<SearchModel> searchModelList;
Context context;
public SearchListAdapter(List<SearchModel> searchModelList, Context context) {
this.searchModelList = searchModelList;
this.context = context;
}
@NonNull
@Override
public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new SearchViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.search_result_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull SearchViewHolder holder, final int position) {
final SearchModel searchModel = searchModelList.get(position);
holder.nameTv.setText(searchModel.getName());
holder.formattedAddress.setText(searchModel.getFormattedAddress());
holder.countryCodeTv.setText(searchModel.getCountryCode());
holder.countryTv.setText(searchModel.getCountry());
// Click listener for Row view
holder.btnGetDirection.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"Position is "+position,Toast.LENGTH_SHORT ).show();
Intent intent = new Intent(context, DirectionActivity.class);
intent.putExtra("latitude",searchModel.getLatitude());
intent.putExtra("longitude",searchModel.getLongitude());
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return searchModelList.size();
}
class SearchViewHolder extends RecyclerView.ViewHolder {
TextView nameTv;
TextView formattedAddress;
TextView countryTv;
TextView countryCodeTv;
LinearLayout row_layout;
Button btnGetDirection;
public SearchViewHolder(@NonNull View itemView) {
super(itemView);
nameTv = itemView.findViewById(R.id.name);
formattedAddress = itemView.findViewById(R.id.formattedAddress);
countryTv = itemView.findViewById(R.id.country);
countryCodeTv = itemView.findViewById(R.id.countryCode);
row_layout = itemView.findViewById(R.id.row_layout);
btnGetDirection = itemView.findViewById(R.id.get_direction);
}
}
}
Step 5: Create row_layout.xml inside layout folder.
Java:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="5dp"
app:cardElevation="5dp"
android:layout_marginBottom="3dp">
<LinearLayout
android:id="@+id/row_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Name: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:paddingLeft="5dp"
android:id="@+id/name"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Address: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:id="@+id/formattedAddress"
android:layout_weight="0.7"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Country: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:id="@+id/country"
android:layout_weight="0.7"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Country code: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/countryCode"
android:paddingLeft="5dp"
android:layout_weight="0.3"/>
<Button
android:id="@+id/get_direction"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/search_btn_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Get Direction"
android:textAllCaps="false"
android:textColor="@color/upsdk_white" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Step 6: Initialize the Recycler view to MainActivity.Java.
Java:
private RecyclerView searchResultList;
searchResultList.setLayoutManager(new LinearLayoutManager(this));
Step 7: On Search button click, search places and set it to ListAdapter.
Java:
mSearchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
searchModelList = new ArrayList<>();
search();
}
});
Java:
public void search() {
TextSearchRequest textSearchRequest = new TextSearchRequest();
textSearchRequest.setQuery(queryInput.getText().toString());
textSearchRequest.setHwPoiType(HwLocationType.ADDRESS);
textSearchRequest.setHwPoiType(HwLocationType.ENTERTAINMENT_PLACE);
textSearchRequest.setHwPoiType(HwLocationType.INDIAN_RESTAURANT);
textSearchRequest.setHwPoiType(HwLocationType.CITIES);
textSearchRequest.setHwPoiType(HwLocationType.REGIONS);
searchService.textSearch(textSearchRequest, new SearchResultListener<TextSearchResponse>() {
@Override
public void onSearchResult(TextSearchResponse textSearchResponse) {
List<Site> sites = textSearchResponse.getSites();
if (sites == null || sites == null || sites.size() <= 0) {
return;
}
AddressDetail addressDetail;
if (sites != null && sites.size() > 0) {
for (Site site : sites) {
searchModel = new SearchModel();
addressDetail = site.getAddress();
searchModel.setName(site.getName());
searchModel.setFormattedAddress(site.getFormatAddress());
searchModel.setCountry(addressDetail.getCountry());
searchModel.setCountryCode(addressDetail.getCountryCode());
searchModel.setLatitude(site.getLocation().getLat());
searchModel.setLongitude(site.getLocation().getLng());
searchModelList.add(searchModel);
}
SearchListAdapter searchListAdapter = new SearchListAdapter(searchModelList, MainActivity.this);
searchResultList.setAdapter(searchListAdapter);
}
}
Now getting the list of places completed.
Result:
Part 2: Map Kit Implementation
This Kit is being used for showing the Huawei map. After clicking on Get Direction button in searched places, its navigates to DirectionActivity.Java which loads the Huawei map using Map Kit and getting the current location using Huawei Location Kit.
Step 1: Create the xml layout which contains the MapView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.huawei.hms.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"/>
</LinearLayout>
Step 2: To use the MapView, implement OnMapReadyCallbackAPI and override the onMapReady(HuaweiMap huaweiMap).
Java:
public class DirectionActivity extends AppCompatActivity implements OnMapReadyCallback{
}
Step 3: Add runtime permissions.
Java:
private static final String[] RUNTIME_PERMISSIONS = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET
};
if (!hasPermissions(this, RUNTIME_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, RUNTIME_PERMISSIONS, REQUEST_CODE);
}
Java:
private static boolean hasPermissions(Context context, String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
Step 4: Load MapView inside onCreate() method of DirectionActivity.Java and call getMapAsync() to register the callback.
Java:
private HuaweiMap hMap;
private MapView mMapView;mMapView = findViewById(R.id.mapView);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_BUNDLE_KEY);
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
Step 5: Inside onMapReady() callback, get the Huawei Map object and set the current location enabled.
Java:
public void onMapReady(HuaweiMap huaweiMap) {
Log.d(TAG, "onMapReady: ");
hMap = huaweiMap;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return;
}
hMap.setMyLocationEnabled(true);
CameraPosition build = new CameraPosition.Builder().target(new LatLng(20.5937, 78.9629)).zoom(4).build();
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(build);
hMap.animateCamera(cameraUpdate);
}
Step 6: Override the onStart(), onStop(),onDestroy(),onPause(), onResume() and onLowMemory() in the DirectionActivity.Java.
Java:
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
protected void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
Now integration of map done.
Result:
Part 3: Location Kit Integration
This kit is being used for getting the current location of the user.
Step 1: Initialize the current location instances.
Java:
private LocationCallback mLocationCallback;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient fusedLocationProviderClient;
private SettingsClient settingsClient;private double latitude;
private double longitude;
Step 2: call getCurrentLocation() inside onCreate() method of DirectionActivity.Java and save it.
Java:
private void getCurrentLocation(){
//create a fusedLocationProviderClient
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
//create a settingsClient
settingsClient = LocationServices.getSettingsClient(this);
mLocationRequest = new LocationRequest();
// set the interval for location updates, in milliseconds.
mLocationRequest.setInterval(10000);
// set the priority of the request
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (null == mLocationCallback) {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
Location loc = locations.get(0);
latitude = loc.getLatitude();
longitude = loc.getLongitude();
if(count == 0){
count = count + 1;
getRoutes();
}
}
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
if (locationAvailability != null) {
boolean flag = locationAvailability.isLocationAvailable();
Toast.makeText(DirectionActivity.this, "isLocationAvailable:"+flag, Toast.LENGTH_SHORT).show();
}
}
};
}
}
Step 3: call requestLocationUpdatesWithCallback() method after getCurrentLocation()insideonCreate().
Java:
private void requestLocationUpdatesWithCallback() {
try {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
LocationSettingsRequest locationSettingsRequest = builder.build();
// check devices settings before request location updates.
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>()
{
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "check location settings success");
// request location updates
fusedLocationProviderClient
.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(DirectionActivity.this,"requestLocationUpdatesWithCallback onFailure:",Toast.LENGTH_SHORT).show();
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e)
{
Toast.makeText(DirectionActivity.this,"checkLocationSetting onFailure:",Toast.LENGTH_SHORT).show();
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(DirectionActivity.this, 0);
} catch (IntentSender.SendIntentException sie) {
Log.e(TAG, "PendingIntent unable to execute request.");
}
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
Now getting current location part completed.
Part 4: Direction API Implementation
This API is getting used for getting the routes between source and destination location.
Step 1: Get the destination location from place info and save it inside onCreate()method of DirectionActivity.java.
Java:
// Destination location data
private double destLatitude;
private double destLongitude;destLatitude = getIntent().getDoubleExtra("latitude",0.0);
destLongitude = getIntent().getDoubleExtra("longitude",0.0);
Step 2: Create DirectionService.java for getting the routes between source and destination location.
Java:
public class DirectionService {
public static final String ROOT_URL = "https://mapapi.cloud.huawei.com/mapApi/v1/routeService/";
public static final String conection = "?key=";
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
final MutableLiveData<JsonData> jsonData = new MutableLiveData<>();
private String jsonResponse;
public RouteInfo info;
private static DirectionService directionService;
public static DirectionService getInstance(){
if (directionService == null)
directionService = new DirectionService();
return directionService;
}
public void setRouteInfo(RouteInfo info)
{
this.info = info;
}
public void driving(String serviceName, String apiKey, Route route) throws UnsupportedEncodingException {
JSONObject json = new JSONObject();
JSONObject origin = new JSONObject();
JSONObject destination = new JSONObject();
try {
origin.put("lng",route.getOrigin().getLng());
origin.put("lat", route.getOrigin().getLat());
destination.put("lng", route.getDestination().getLng());
destination.put("lat", route.getDestination().getLat());
json.put("origin", origin);
json.put("destination", destination);
} catch (JSONException e) {
Log.e("error", e.getMessage());
}
RequestBody body = RequestBody.create(JSON, String.valueOf(json));
OkHttpClient client = new OkHttpClient();
Request request =
new Request.Builder().url(ROOT_URL + serviceName + conection + URLEncoder.encode(apiKey, "UTF-8"))
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("driving", e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// Log.d("driving", response.body().string());
info.routeInfo(response.body().string());
}
});
}
}
Step 3: Call the getRoute() method inside getCurrentLocation() of DirectionActivity.Java.
Java:
private void getRoutes()
{
// get the routes
Origin origin = new Origin();
origin.setLat(latitude);
origin.setLng(longitude);
Destination dest = new Destination();
dest.setLat(destLatitude);
dest.setLng(destLongitude);
Route route = new Route();
route.setOrigin(origin);
route.setDestination(dest);
try {
DirectionService.getInstance().setRouteInfo(this);
DirectionService.getInstance().driving("driving",MainActivity.MY_API_KEY,route);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
Step 4: Create an Interface RouteInfo.
Java:
public interface RouteInfo {
void routeInfo(String info);
}
Step 5: DirectionActivity.Java will implement RouteInfo interface.
Java:
public class DirectionActivity extends AppCompatActivity implements OnMapReadyCallback,RouteInfo{
}
Step 6: Override routeInfo() method and convert string response to Json object using gson library.
Java:
@Override
public void routeInfo(String info) {
Gson gson = new Gson();
JsonData obj = gson.fromJson(info,JsonData.class);
addPolyline(obj);
addMarker();
animateCameraToCurrentLocation();
}
Step 7: Add polyline from the routes info.
Java:
private void addPolyline(JsonData obj) {
if(hMap == null){
return;
}
if (null != mPolyline) {
mPolyline.remove();
mPolyline = null;
}
PolylineOptions options = new PolylineOptions();
if(obj != null){
ArrayList<Routes> routes = obj.getRoutes();
if(routes != null && routes.size() > 0){
ArrayList<Path> paths = routes.get(0).getPaths();
if(paths != null && paths.size() > 0){
ArrayList<Step> steps = paths.get(0).getSteps();
if(steps != null && steps.size() > 0)
{
for(Step step : steps) {
ArrayList<com.huawei.sitekitsampleapp.model.Polyline> polylines = step.getPolyline();
if(polylines != null && polylines.size() > 0){
for(com.huawei.sitekitsampleapp.model.Polyline polyline : polylines){
// Add lat lng to options
options.add(new LatLng(polyline.getLat(),polyline.getLng()));
}
}
}
}
}
}
}
options.color(Color.GREEN).width(3);
mPolyline = hMap.addPolyline(options);
}
Step 8: Add a Marker at destination location.
Java:
private void addMarker() {
if (null != mMarker) {
mMarker.remove();
}
MarkerOptions options = new MarkerOptions()
.position(new LatLng(destLatitude, destLongitude)).icon(BitmapDescriptorFactory.fromResource(R.drawable.marker));
mMarker = hMap.addMarker(options);
}
Step 9: Add marker.xml to drawable folder.
XML:
<vector android:height="24dp" android:tint="#FF1730"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>
Step 10: Animate camera to current location.
Java:
private void animateCameraToCurrentLocation()
{
CameraPosition build = new CameraPosition.Builder().target(new LatLng(latitude, longitude)).zoom(13).build();
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(build);
hMap.animateCamera(cameraUpdate);
}
Now showing the direction between two place done.
Result:
Tips and Tricks:
Set input properly for origin and Destination for getting the routes.
Conclusion:
This application can help to show the direction between your current location to your favorite place. You can get the restaurants, schools and places and navigate to the same.
Reference:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/directions-walking-0000001050161494-V5#EN-US_TOPIC_0000001050161494__section12176172981317
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-keyword-search-0000001050156630
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-map-instance-creation-0000001062881706
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/location-develop-steps-0000001050746143
Introduction
In this article, we will learn how to implement Huawei Network kit in Android. Network kit is a basic network service suite we can utilizes scenario based REST APIs as well as file upload and download. The Network kit can provide with easy-to-use device-cloud transmission channels featuring low latency and high security.
{
"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"
}
About Huawei Network kit
Huawei Network Kit is a service 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, uploads and downloads. With Huawei Network Kit we can improve the network connection when you want to access to a URL.
Supported Devices
Huawei Network Kit is not for all devices, so first we need to validate if the device support or not, and here is the list of devices supported.
Requirements
1. Any operating system (i.e. MacOS, Linux and Windows).
2. Any IDE with Android SDK installed (i.e. IntelliJ, Android Studio).
3. Minimum API Level 19 is required.
4. Required EMUI 3.0 and later version devices.
Code Integration
Create Application in Android Studio.
App level gradle dependencies.
Code:
apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
Gradle dependencies
Code:
implementation "com.huawei.hms:network-embedded:5.0.1.301"
implementation "androidx.multidex:multidex:2.0.1"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
Root level gradle dependencies
Code:
maven {url 'https://developer.huawei.com/repo/'}
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
Add the below permissions in Android Manifest file.
Code:
<manifest xlmns:android...>
...
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application ...
</manifest>
First we need to implement HMS Network kit with this we will check Network kit initialization status.
Code:
public class HWApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
public void onCreate() {
super.onCreate();
NetworkKit.init(getApplicationContext(), new NetworkKit.Callback() {
@Override
public void onResult(boolean status) {
if (status) {
Toast.makeText(getApplicationContext(), "Network kit successfully initialized", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Network kit initialization failed", Toast.LENGTH_SHORT).show();
}
}
});
}
}
Now we need to create ApiClient class, here we will declare the Restclient object.
Code:
public class ApiClient {
private static final String BASE_URL = "https://newsapi.org/v2/";
private static final int CONNECT_TIMEOUT = 10000;
private static final int WRITE_TIMEOUT = 1000;
private static final int TIMEOUT = 10000;
public static RestClient restClient;
public static RestClient getRestClient() {
if (restClient == null) {
restClient = new RestClient
.Builder()
.baseUrl(BASE_URL)
.httpClient(getHttpClient())
.build();
}
return restClient;
}
public static HttpClient getHttpClient() {
return new HttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT)
.readTimeout(TIMEOUT)
.writeTimeout(WRITE_TIMEOUT)
.enableQuic(false)
.build();
}
}
Code:
public interface ApiInterface {
@GET("top-headlines")
Submit<String> getMovies(@Query("country") String country, @Query("category") String category, @Query("apiKey") String apiKey);
}
In Our MainActivity.java class we need to create the instance for ApiInterface, now we need to call the Restclient object to send synchronous or asynchronous requests.
Java:
public class MainActivity extends AppCompatActivity {
ApiInterface apiInterface;
private RecyclerView recyclerView;
private List<NewsInfo.Article> mArticleList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
init();
}
private void init() {
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.smoothScrollToPosition(0);
}
@Override
protected void onResume() {
super.onResume();
loadData();
}
private void loadData() {
apiInterface = ApiClient.getRestClient().create(ApiInterface.class);
apiInterface.getMovies("us", "business", "e4d3e43d2c0b4e2bab6500ec6e469a94")
.enqueue(new Callback<String>() {
@Override
public void onResponse(Submit<String> submit, Response<String> response) {
runOnUiThread(() -> {
Gson gson = new Gson();
NewsInfo newsInfo = gson.fromJson(response.getBody(), NewsInfo.class);
mArticleList = newsInfo.articles;
recyclerView.setAdapter(new ContentAdapter(getApplicationContext(), mArticleList));
recyclerView.smoothScrollToPosition(0);
});
}
@Override
public void onFailure(Submit<String> submit, Throwable throwable) {
Log.i("TAG", "Api failure");
}
});
}
}
NewsInfo.java
Java:
public class NewsInfo {
@SerializedName("status")
public String status;
@SerializedName("totalResults")
public Integer totalResults;
@SerializedName("articles")
public List<Article> articles = null;
public class Article {
@SerializedName("source")
public Source source;
@SerializedName("author")
public String author;
@SerializedName("title")
public String title;
@SerializedName("description")
public String description;
@SerializedName("url")
public String url;
@SerializedName("urlToImage")
public String urlToImage;
@SerializedName("publishedAt")
public String publishedAt;
@SerializedName("content")
public String content;
public String getAuthor() {
return author;
}
public String getTitle() {
return title;
}
public class Source {
@SerializedName("id")
public Object id;
@SerializedName("name")
public String name;
public String getName() {
return name;
}
}
}
}
main_activity.xml
XML:
<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=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="#f2f2f2"
tools:showIn="@layout/activity_main" />
</androidx.constraintlayout.widget.ConstraintLayout>
ContentAdapter.java
Code:
public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {
private List<NewsInfo.Article> newsInfos;
private Context context;
public ContentAdapter(Context applicationContext, List<NewsInfo.Article> newsInfoArrayList) {
this.context = applicationContext;
this.newsInfos = newsInfoArrayList;
}
@Override
public ContentAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_adapter, viewGroup, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ContentAdapter.ViewHolder viewHolder, int i) {
viewHolder.chanelName.setText(newsInfos.get(i).source.getName());
viewHolder.title.setText(newsInfos.get(i).getTitle());
viewHolder.author.setText(newsInfos.get(i).getAuthor());
Glide.with(context).load(newsInfos.get(i).urlToImage).into(viewHolder.imageView);
}
@Override
public int getItemCount() {
return newsInfos.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView chanelName, author, title;
private ImageView imageView;
public ViewHolder(View view) {
super(view);
chanelName = view.findViewById(R.id.chanelName);
author = view.findViewById(R.id.author);
title = view.findViewById(R.id.title);
imageView = view.findViewById(R.id.cover);
itemView.setOnClickListener(v -> {
});
}
}
}
layout_adapter.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView android:layout_width="match_parent"
android:layout_height="150dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:clickable="true"
android:focusable="true"
android:elevation="60dp"
android:foreground="?android:attr/selectableItemBackground"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/cover"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:scaleType="fitXY" />
<TextView
android:id="@+id/chanelName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/cover"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:textStyle="bold" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/cover"
android:layout_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_below="@id/chanelName" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/cover"
android:layout_marginLeft="20dp"
android:layout_marginTop="25dp"
android:layout_below="@id/chanelName" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
Demo
Tips and Tricks
1. Add latest Network kit dependency.
2. Minimum SDK 19 is required.
3. Do not forget to add Internet permission in Manifest file.
4. Before sending request you can check internet connection.
Conclusion
That’s it!
This article will help you to use Network kit in your android application, as we have implemented REST API. We can get the data using either HttpClient object or RestClient object.
Thanks for reading! If you enjoyed this story, please click the Like button and Follow. Feel free to leave a Comment below.
Reference
Network kit URL
Original Source
{
"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 Introduction
In this article, we will develop Music Station android app for Huawei Vision S (Smart TV) devices. Huawei Vision S is a brand new large screen category and important part of Huawei's "1+8+N" full-scenario services and Huawei Developer Ecosystem. Since, Huawei Vision S system architecture supports AOSP project framework, we used Leanback Library which offers extensive features for large screens to develop our user experience.
Why an app for Huawei Vision S?
Large screen offers better visibility and enhanced user experience. Due to Covid-19 lockdown, Smart TV has grown to include over 80% more users than it had this time last year. Total distribution of usage for TV is increasing rapidly. As a result of this, total number of TV apps has jumped dramatically including educational and entertainment apps.
Designing App for Huawei Vision S
While designing an app for Huawei Vision S, we have to keep following key points in our mind:
Build Layout for TV: We must design landscape orientation layout that allows users to easily see the screen 10 feet away from the TV.
Management Controller: Our app must support arrow keys and handle offline controllers as well as inputs from multiple controllers.
For this article, we implemented Leanback Library which offers amazing and interactive user experience for apps such as Audio/Video players and so on.
Pre-Requisites
Before getting started, following are the requirements:
Android Studio (During this tutorial, we used version 4.1.1)
Android SDK 24 or later
Huawei Vision S for testing
Development
Following are the major steps of development for this article:
Step 1: Add Dependencies & Permissions
1.1: Add the following dependencies in the app level build.gradle file:
Java:
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// TV Libs
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.leanback:leanback-preference:1.0.0'
// General
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.nabinbhandari.android:permissions:3.8'
// Animation
implementation 'com.airbnb.android:lottie:3.7.0'
implementation 'com.gauravk.audiovisualizer:audiovisualizer:0.9.2'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation ('com.github.bumptech.glide:okhttp3-integration:4.12.0'){
exclude group: 'glide-parent'
}
}
1.2: We are developing this app only for TV. So, we will disable the Touch_Screen requirements and enable the Leanback. For the audio visualizer, we need Record_Audio permission. Add the following permissions and features tag in the AndroidManifest.xml:
Java:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="true" />
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
1.3: Huawei Vision S supports Leanback Library but does not supports Leanback Launcher. So, we added the following tag in the SplashActivity inside the AndroidManifest.xml:
Code:
<activity android:name=".activities.SplashActivity"
android:screenOrientation="landscape"
android:banner="@drawable/app_icon"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:launchMode="singleTop"
android:logo="@drawable/app_icon"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- HUAWEI Vision S only support CATEGORY_LAUNCHER -->
<category android:name="android.intent.category.LAUNCHER" />
<!-- ADD the below line only if you want to release the same code on Google Play -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
1.4: The banner icon is required when we are developing apps for TV. The size for Huawei Vision S banner icon is 496x280 which must be added in the drawable folder.
Step 2: Generating Supported Language JSON
Since our main goal is playing Music, we restricted data source and generated a JSON file locally to avoid API creation and API calling. In real world scenario, an API can be developed or can be used to get the real-time data.
Step 3: Building Layout
3.1: The most important layout of our application is of PlayerActivity. Add the following activity_player.xml layout file in the layout folder of the res. We developed this layout to enhance user experience and add custom views like Audio Visualizer. The layout has two main sub-layouts, Player Content View and Error View.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background"
android:keepScreenOn="true">
<ImageView
android:id="@+id/imgBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="false"
app:lottie_progress="53"
app:lottie_rawRes="@raw/splash_animation" />
<com.gauravk.audiovisualizer.visualizer.CircleLineVisualizer
android:id="@+id/blastVisualizer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:avColor="@color/light_red"
custom:avDensity="0.8"
custom:avSpeed="normal"
custom:avType="fill" />
<ImageView
android:id="@+id/imgOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:background="@android:color/black" />
<ProgressBar
android:id="@+id/progressBarLoader"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimaryDark"
android:indeterminateTintMode="src_atop"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/rlContentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:paddingStart="50dp"
android:paddingEnd="50dp">
<ImageView
android:id="@+id/imgIcon"
android:layout_width="80dp"
android:layout_height="80dp" />
<TextView
android:id="@+id/txtSongName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/imgIcon"
android:layout_marginTop="5dp"
android:textSize="@dimen/title_text_size" />
<TextView
android:id="@+id/txtArtistName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtSongName"
android:layout_marginTop="5dp"
android:textSize="@dimen/artist_text_size" />
<TextView
android:id="@+id/txtCategoryName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtArtistName"
android:layout_marginTop="5dp"
android:textSize="@dimen/category_text_size" />
<ImageButton
android:id="@+id/btnPlayPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtCategoryName"
android:layout_marginTop="50dp"
android:background="@drawable/ui_selector_bg"
android:focusable="true"
android:src="@drawable/lb_ic_play" />
<SeekBar
android:id="@+id/seekBarAudio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/btnPlayPause"
android:layout_marginTop="10dp"
android:background="@drawable/ui_selector_bg"
android:colorControlActivated="@color/white"
android:focusable="true"
android:progressTint="@color/white"
android:thumbTint="@color/white" />
<TextView
android:id="@+id/txtTotalDuration"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_alignEnd="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:text=" / -"
android:textSize="@dimen/time_text_size" />
<TextView
android:id="@+id/txtCurrentTime"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:layout_toStartOf="@+id/txtTotalDuration"
android:text="-"
android:textSize="@dimen/time_text_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rlErrorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:visibility="gone">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<ImageView
android:id="@+id/imgErrorLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/lb_ic_sad_cloud" />
<TextView
android:id="@+id/txtError"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imgErrorLoading"
android:layout_centerHorizontal="true"
android:text="@string/error_message"
android:textSize="@dimen/time_text_size" />
<Button
android:id="@+id/btnDismiss"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtError"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:focusable="true"
android:text="@string/dismiss_error" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
3.2: In this article, we used Lottie animation in the SplashActivity for better user experience inside the following activity_splash.xml.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/colorPrimaryDark"
tools:context=".activities.SplashActivity">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="true"
app:lottie_repeatCount="1"
app:lottie_rawRes="@raw/splash_animation" />
<ImageView
android:id="@+id/imgAppIcon"
android:src="@drawable/img_music_station_logo"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="40dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Step 4: Adding JAVA Classes
4.1: We extended the MainFragment class from BrowseFragment which is Leanback home layout component. This view handles the landing screen containing interactive side menu, main content area with different cards and navigation between them.
Java:
public class MainFragment extends BrowseFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setupEventListeners();
}
private void loadRows() {
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
CardPresenter cardPresenter = new CardPresenter();
int i;
for (i = 0; i < DataUtil.getData(getActivity()).size(); i++) {
CategoryModel categoryModel = DataUtil.getData(getActivity()).get(i);
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
for (int j = 0; j < categoryModel.getCategorySongs().size(); j++) {
listRowAdapter.add(categoryModel.getCategorySongs().get(j));
}
HeaderItem header = new HeaderItem(i, categoryModel.getCategoryName());
rowsAdapter.add(new ListRow(header, listRowAdapter));
}
setAdapter(rowsAdapter);
}
private void prepareBackgroundManager() {
BackgroundManager mBackgroundManager = BackgroundManager.getInstance(getActivity());
mBackgroundManager.attach(getActivity().getWindow());
mBackgroundManager.setColor(getResources().getColor(R.color.main_background));
DisplayMetrics mMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
private void setupUIElements() {
setTitle(getString(R.string.app_name));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(true);
setBrandColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
setSearchAffordanceColor(ContextCompat.getColor(getActivity(), R.color.colorPrimary));
}
private void setupEventListeners() {
setOnItemViewClickedListener(new ItemViewClickedListener());
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof Song) {
Song song = (Song) item;
Intent intent = new Intent(getActivity(), PlayerActivity.class);
intent.putExtra(DataUtil.SONG_DETAIL, song);
getActivity().startActivity(intent);
}
}
}
}
4.2: Cards are displayed using CardPresenter which is extended by Presenter from the Leanback library.
Java:
public class CardPresenter extends Presenter {
private static final int CARD_WIDTH = 313;
private static final int CARD_HEIGHT = 176;
private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
view.setBackgroundColor(color);
view.findViewById(R.id.info_field).setBackgroundColor(color);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
sDefaultBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.background);
sSelectedBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.colorPrimaryDark);
ImageCardView cardView =
new ImageCardView(parent.getContext()) {
@Override
public void setSelected(boolean selected) {
updateCardBackgroundColor(this, selected);
super.setSelected(selected);
}
};
cardView.setFocusable(true);
cardView.setFocusableInTouchMode(true);
updateCardBackgroundColor(cardView, false);
return new ViewHolder(cardView);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
Song song = (Song) item;
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setTitleText(song.getSongName());
cardView.setContentText(song.getArtistName());
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
Glide.with(viewHolder.view.getContext())
.load(song.getImageURL())
.centerCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(cardView.getMainImageView());
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setBadgeImage(null);
cardView.setMainImage(null);
}
}
4.3: Whenever user click on any Card Item, PlayerActivity is opened. Following are some of the important functions of the PlayerActivity.java. Please refer to the github link for complete code of this class.
Java:
private void handlePlayer(){
if (currentState == MediaPlayerHolder.PlayerState.PLAYING) {
pauseSong();
} else if (currentState == MediaPlayerHolder.PlayerState.PAUSED ||
currentState == MediaPlayerHolder.PlayerState.COMPLETED ||
currentState == MediaPlayerHolder.PlayerState.RESET) {
playSong();
}
}
private void setUI() {
if (song != null) {
txtSongName.setText(song.getSongName());
txtArtistName.setText(song.getArtistName());
txtCategoryName.setText(song.getCategoryName());
Glide.with(this)
.load(song.getImageURL())
.centerCrop()
.circleCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(imgIcon);
}
setupSeekBar();
}
private void pauseSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.PausePlayback());
}
private void playSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.StartPlayback());
}
private void resetSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.ResetPlayback());
}
public void log(StringBuffer formattedMessage) {
Log.d(PlayerActivity.class.getSimpleName(), String.format("log: %s", formattedMessage));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.UpdateLog event) {
log(event.formattedMessage);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackDuration event) {
seekBarAudio.setMax(event.duration);
setTotalDuration(event.duration);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackPosition event) {
if (!isUserSeeking) {
seekBarAudio.setProgress(event.position, true);
updateProgressTime(event.position);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.StateChanged event) {
hideLoader();
currentState = event.currentState;
switch (event.currentState) {
case PLAYING:
btnPlayPause.setImageResource(R.drawable.lb_ic_pause);
if (mMediaPlayerHolder.getAudioSessionId() != -1 && blastVisualizer != null){
blastVisualizer.setAudioSessionId(mMediaPlayerHolder.getAudioSessionId());
blastVisualizer.show();
}
break;
case PAUSED:
case RESET:
case COMPLETED:
btnPlayPause.setImageResource(R.drawable.lb_ic_play);
if (blastVisualizer != null){
blastVisualizer.hide();
}
break;
case ERROR:
showError();
break;
}
}
private void showError() {
if(song != null){
String title = song.getSongName();
String artist = song.getArtistName();
String songName = "Unable to play " + title + " (" + artist + ")";
txtError.setText(songName);
}
rlErrorLayout.setVisibility(View.VISIBLE);
btnDismiss.requestFocus();
}
private void setTotalDuration(int duration) {
long totalSecs = TimeUnit.MILLISECONDS.toSeconds(duration);
long hours = totalSecs / 3600;
long minutes = (totalSecs % 3600) / 60;
long seconds = totalSecs % 60;
String totalDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
totalDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
String text = " / " + totalDuration;
txtTotalDuration.setText(text);
}
private void updateProgressTime(int position){
long currentSecs = TimeUnit.MILLISECONDS.toSeconds(position);
long hours = currentSecs / 3600;
long minutes = (currentSecs % 3600) / 60;
long seconds = currentSecs % 60;
String currentDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
currentDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
txtCurrentTime.setText(currentDuration);
}
4.4: We used EventBus to asynchronously notify the PlayerActivity UI changes. The MediaPlayerHolder.java class handles the MediaPlayer states and manages the functionalities like playing, pause and seekbar position updates. Please refer to the github link for complete code of this class.
Java:
public int getAudioSessionId(){
if(mMediaPlayer != null){
return mMediaPlayer.getAudioSessionId();
} else
return -1;
}
public void release() {
logToUI("release() and mMediaPlayer = null");
mMediaPlayer.release();
EventBus.getDefault().unregister(this);
}
public void stop() {
logToUI("stop() and mMediaPlayer = null");
mMediaPlayer.stop();
}
public void play() {
if (!mMediaPlayer.isPlaying()) {
logToUI(String.format("start() %s", urlPath));
mMediaPlayer.start();
startUpdatingSeekbarWithPlaybackProgress();
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
}
}
public void pause() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
logToUI("pause()");
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PAUSED));
}
}
public void reset() {
logToUI("reset()");
mMediaPlayer.reset();
load(urlPath);
stopUpdatingSeekbarWithPlaybackProgress(true);
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.RESET));
}
public void load(String url) {
this.urlPath = url.replaceAll(" ", "%20");
if (mMediaPlayer != null) {
try {
mMediaPlayer.setAudioAttributes(
new AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer player) {
initSeekbar();
play();
}
});
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
return false;
}
});
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer arg0) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
SystemClock.sleep(200);
mMediaPlayer.start();
}
});
logToUI("load() {1. setDataSource}");
mMediaPlayer.setDataSource(urlPath);
logToUI("load() {2. prepare}");
mMediaPlayer.prepareAsync();
} catch (Exception e) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
logToUI(e.toString());
}
}
}
public void seekTo(int duration) {
logToUI(String.format(Locale.ENGLISH, "seekTo() %d ms", duration));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mMediaPlayer.seekTo(duration, MediaPlayer.SEEK_CLOSEST);
else
mMediaPlayer.seekTo(duration);
}
private void stopUpdatingSeekbarWithPlaybackProgress(boolean resetUIPlaybackPosition) {
if (mExecutor != null) {
mExecutor.shutdownNow();
}
mExecutor = null;
mSeekbarProgressUpdateTask = null;
if (resetUIPlaybackPosition) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackPosition(0));
}
}
private void startUpdatingSeekbarWithPlaybackProgress() {
// Setup a recurring task to sync the mMediaPlayer position with the Seekbar.
if (mExecutor == null) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
}
if (mSeekbarProgressUpdateTask == null) {
mSeekbarProgressUpdateTask = new Runnable() {
@Override
public void run() {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
int currentPosition = mMediaPlayer.getCurrentPosition();
EventBus.getDefault().post(
new LocalEventFromMediaPlayerHolder.PlaybackPosition(
currentPosition));
}
}
};
}
mExecutor.scheduleAtFixedRate(
mSeekbarProgressUpdateTask,
0,
SEEKBAR_REFRESH_INTERVAL_MS,
TimeUnit.MILLISECONDS
);
}
public void initSeekbar() {
// Set the duration.
final int duration = mMediaPlayer.getDuration();
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackDuration(duration));
logToUI(String.format(Locale.ENGLISH, "setting seekbar max %d sec", TimeUnit.MILLISECONDS.toSeconds(duration)));
}
When user click Select button on the Remote Control of Huawei Vision S on any item, the player view is opened and MediaPlayer start loading the song url. Once the song is loaded, it starts playing and the icon of Play changes to Pause. The Audio Visualizer takes AudioSessionId to sync with audio song. By default, seekbar is selected for the user to skip the songs using Right and Left buttons of the Remote Control. The duration of the song updates based on seekbar position.
Step 5: Run the application
We have added all the required code. Now, just build the project, run the application and test on Huawei Vision S.
Conclusion
Using Leanback, developers can develop beautifully crafted android applications with amazing UI/UX experience for Huawei Vision S. They can also enhance their user engagement and behavior. Combining different Huawei Kits supported by Vision S like Account or IAP can yield amazing results.
Tips and Tricks
You must use default Launcher if you are developing app for Huawei Vision S.
Leanback Launcher is not supported by Huawei Vision S.
If you have same code base for Mobile and Vision S devices, you can use TVUtils class to check at run-time about the device and offer functionalities based on it.
Make sure to add all the permissions like RECORD_AUDIO, INTERNET.
Make sure to add run-time permissions check. In this article, we used 3rd party Permission Check library with custom Dialog if user deny any of the required permission.
Always use animation libraries like Lottie or ViewAnimator to enhance UI/UX in your application.
We used AudioVisualizer library to bring Music feel on our Player UI.
References
Android TV Documentation:
https://developer.android.com/training/tv/start
https://developer.android.com/training/tv/start/layouts
https://developer.android.com/training/tv/start/controllers
https://developer.android.com/training/tv/start/navigation
Lottie Android Documentation:
http://airbnb.io/lottie/#/android
Github Code Link:
https://github.com/yasirtahir/MusicStationTV
Original Source