[Huawei Developers] John Wo:
Introduction

Search!! It’s a sought-after word through the entire software evolution and industry.

What is searching?

Searching is a process of locating a particular element present in a given set of elements. The element may be a record, a table, or a file or a keyword.

What is Search Engine?

A program that searches for and identifies items in a database that correspond to keywords or characters specified by the user, used especially for finding particular sites on the World Wide Web.

“Archie” was the first search engine and it was introduced in 1990 by Alan Entage.

In today’s technical era, search engines has been drastically evolved and it is not limited to only big systems and laptops, their reach is now on your small mobile phone as well.

In fact, you can create your own search engine to have the upper hand on your business in this competitive era.

Huawei Search Kit offers API’s to build your own search platform in Android environment

Huawei Search Kit leverage developers to use the Petal Search capabilities by providing the device-side SDK and cloud side API’s to efficiently enable the search operation on the mobile app.

Note: Petal Search is a mobile search engine of Huawei powered by such cutting-edge technologies as big data, AI, and NLP.

In a typical scenario, any search engine works on the same principles shown in following image.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106104611.55833268467831601875211849993071:50511118054909:2800:EDA7BC95DCEF79AB0E21C4394107492A1941567B18203FE17CF2263CDC718471.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106104611.55833268467831601875211849993071:50511118054909:2800:EDA7BC95DCEF79AB0E21C4394107492A1941567B18203FE17CF2263CDC718471.png[/IMG]

Huawei Search Kit uses the Petal Search engine capabilities in similar fashion by feeding the URL’s to engine’s scheduler which further do the parsing and indexing using Petal search and provide the search results which can be handled the mobile application.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106114756.61945815702581327731276128048769:50511118054909:2800:272F707664D0BEB99579183A91718BD8D21098A1A518FC35426E8FE56BB6C0BE.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106114756.61945815702581327731276128048769:50511118054909:2800:272F707664D0BEB99579183A91718BD8D21098A1A518FC35426E8FE56BB6C0BE.png[/IMG]

Advantages

1. Speedy application development

2. Efficient search response

3. Cost effective

Note: Huawei Search Kit provides Client and Server side API’s to implement the search platform for your websites.

Responsibilities

To develop end to end solution and implement your own mobile search engine application, following would be your responsibilities:

1. Sites/Websites needs to be developed and maintained by the developers.

2. Receiving and handling the search queries from the end users and transporting them to Huawei for further search operation.

3. Developers are responsible to handle all the communication, modification or removal of the search queries raised by user regarding your website.

4. All the queries sent from your website to the Huawei search kit/engine shall comply all the technical guidelines provided by Huawei.

Development Overview

Prerequisite

1. Must have a Huawei Developer Account

2. Must have Android Studio 3.0 or later

3. Must have a Huawei phone with HMS Core 4.0.2.300 or later

4. EMUI 3.0 or later

Software Requirements

1. Java SDK 1.7 or later

2. Android 5.0 or later

Preparation

1. Create an app or project in the Huawei App Gallery Connect.

2. Provide the SHA Key and App Package name of the project in App Information Section and enable the Search Kit API.

3. Download the agconnect-services.json file.

4. Create an Android project.

Integration

1. Add below to build.gradle (project)file, under buildscript/repositories and allprojects/repositories.

Maven {url 'http://developer.huawei.com/repo/'}

2. Add below to build.gradle (app) file, under dependencies to use the search kit SDK.

dependencies{
// Import the SDK.
implementation com.huawei.hms:searchkit:5.0.4.303.
}

Tip: Android minimum version supported by Search kit is 24.

Configuring Network Permissions

To allow HTTP network requests on devices with target version 28 or later, configure the following information in the AndroidManifest.xml file:

<application
android:usesCleartextTraffic="true">
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Development Process

This article focuses on demonstrating the capabilities of Huawei Search Kit using device side SDK and API’s only.

We will understand the server/website configuration and Search Kit capabilities in next article.

Use Case: The application developed and explained is a very simple mobile search application to web search the products on a associated application.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106120605.70692384044613945051133167539351:50511118054909:2800:FFA5FACDC36636BA62CB5C8BB1C2675261AFA2A54E97F29AD4C0F39540318D76.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106120605.70692384044613945051133167539351:50511118054909:2800:FFA5FACDC36636BA62CB5C8BB1C2675261AFA2A54E97F29AD4C0F39540318D76.png[/IMG]

This article explains client side search capabilities by implementing a mobile search application which only uses the web search API’s and return the search results from the website.

Client Side Development

Initializing Search Kit

You can call SearchKitInstance.init() in an Activity to initialize the SDK.

import com.huawei.hms.searchkit.SearchKitInstance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);

// appID is obtained after your app is created in AppGallery Connect. Its value is of the string type.
// appID is the second parameter that requires to be passed to the init API to initialize Search Kit.
SearchKitInstance.init(this, "103029525");
}
}

Search Activity

Search activity is responsible for:

1 Creating WebSearchRequest() for custom search

public static final WebSearchRequest webRequest = new WebSearchRequest(); webRequest.setQ(query);
webRequest.setLang(Language.ENGLISH);
webRequest.setLang(Language.FRENCH);
webRequest.setSregion(Region.INDIA);
webRequest.setPn(1);
webRequest.setPs(10);
webRequest.setWithin("www.fragrancespecialities.com");

commonRequest.setQ(query);
commonRequest.setLang(Language.ENGLISH);
commonRequest.setLang(Language.FRENCH);
commonRequest.setSregion(Region.INDIA);
commonRequest.setPn(1);
commonRequest.setPs(10);

2 Setting request token for the calling the search results through website

setInstanceCredential() is used to set a global token, which is accessible to all methods. The token is used to verify a search request on the server. Search results of the request are returned only after the verification is successful.

if (tokenResponse.getAccess_token() != null) {
SearchKitInstance.getInstance().setInstanceCredential(tokenResponse.getAccess_token());
}

3 Setting request token for web search

getWebSearcher() api is used to search for the web results.

SearchKitInstance.getInstance().getWebSearcher().search(webRequest);

4 Start a web page search

webSearchRequest() is used as parameter which was constructed in Step 1 to the search method.

BaseSearchResponse<List<WebItem>> webResponse =
SearchKitInstance.getInstance().getWebSearcher().search(webRequest);

Complete Code

import android.content.Context;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

public class SearchActivity extends AppCompatActivity {
private static final String TAG = SearchActivity.class.getSimpleName();
private EditText editSearch;
private LinearLayout linearSpellCheck, linearViewPager;
private RecyclerView recyclerSuggest, recyclerContent;
private ViewPager viewPager;
private TabLayout tabLayout;
private ViewPagerAdapter viewPagerAdapter;
private SuggestAdapter suggestAdapter;
private TextView tvSpellCheck;
public static int mCurrentPage = 0;
public static final WebSearchRequest webRequest = new WebSearchRequest();
public static final CommonSearchRequest commonRequest = new CommonSearchRequest();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);

SearchKitInstance.enableLog();
SearchKitInstance.init(this, "103029525");

initRetrofit();
initView();
}

private void initView() {
editSearch = findViewById(R.id.search_view);
linearSpellCheck = findViewById(R.id.linear_spell_check);
recyclerSuggest = findViewById(R.id.suggest_list);
recyclerContent = findViewById(R.id.content_list);
tvSpellCheck = findViewById(R.id.tv_spell_check);
viewPager = findViewById(R.id.view_pager);
tabLayout = findViewById(R.id.tab_layout);
linearViewPager = findViewById(R.id.linear_view_pager);

LinearLayoutManager layoutManager = new LinearLayoutManager(SearchActivity.this);
recyclerSuggest.setLayoutManager(layoutManager);

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(SearchActivity.this);
recyclerContent.setLayoutManager(linearLayoutManager);

FragmentManager fm = getSupportFragmentManager();
if (null == viewPagerAdapter) {
viewPagerAdapter = new ViewPagerAdapter(fm, this);
}
viewPager.setAdapter(viewPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
viewPager.setOffscreenPageLimit(3);

initViewPagerListener();
onEditTextListener();
}

private void initViewPagerListener() {
viewPager.addOnPageChangeListener(
new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}

@Override
public void onPageSelected(int position) {
mCurrentPage = position;
}

@Override
public void onPageScrollStateChanged(int state) {}
});
}

private void onEditTextListener() {
editSearch.addTextChangedListener(
new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
recyclerSuggest.setVisibility(View.VISIBLE);
linearSpellCheck.setVisibility(View.GONE);

linearViewPager.setVisibility(View.GONE);

if (!TextUtils.isEmpty(s.toString())) {
getSuggest(s.toString());
} else {
recyclerSuggest.setVisibility(View.GONE);
linearViewPager.setVisibility(View.VISIBLE);
if (TextUtils.isEmpty(tvSpellCheck.getText().toString())) {
linearSpellCheck.setVisibility(View.GONE);
} else {
linearSpellCheck.setVisibility(View.VISIBLE);
}
if (suggestAdapter != null) {
suggestAdapter.clear();
}
}
}

@Override
public void afterTextChanged(Editable s) {}
});

editSearch.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
recyclerSuggest.setVisibility(View.GONE);
linearViewPager.setVisibility(View.VISIBLE);

hintSoftKeyboard();
getSpellCheck(v.getText().toString());
return true;
}
return false;
}
});
}

private void getSpellCheck(final String query) {
Observable.create(
new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
SpellCheckResponse response =
SearchKitInstance.getInstance()
.getSearchHelper()
.spellCheck(query, Language.ENGLISH);
if (response != null && response.getCorrectedQuery() != null) {
emitter.onNext(response.getCorrectedQuery());
} else {
Log.e(TAG, "spell error");
emitter.onNext("");
}
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
if (TextUtils.isEmpty(s)) {
linearSpellCheck.setVisibility(View.GONE);
} else {
linearSpellCheck.setVisibility(View.VISIBLE);
tvSpellCheck.setText(s);
}
doSearch(query);
}
},
StaticUtils.consumer);
}

private void getSuggest(final String query) {
Observable.create(
new ObservableOnSubscribe<List<String>>() {
@Override
public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
AutoSuggestResponse response =
SearchKitInstance.getInstance()
.getSearchHelper()
.suggest(query, Language.ENGLISH);
List<String> list = new ArrayList<String>();
if (response != null) {
if (response.getSuggestions() != null && !response.getSuggestions().isEmpty()) {
for (int i = 0; i < response.getSuggestions().size(); i++) {
list.add(response.getSuggestions().get(i).getName());
}
emitter.onNext(list);
}
}
emitter.onComplete();
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Consumer<List<String>>() {
@Override
public void accept(List<String> list) throws Exception {
if (suggestAdapter != null) {
suggestAdapter.clear();
}
suggestAdapter = new SuggestAdapter(list);
recyclerSuggest.setAdapter(suggestAdapter);

suggestAdapter.setOnClickListener(
new SuggestAdapter.OnItemClickListener() {
@Override
public void click(String text) {
doSearch(text);
editSearch.setText(text);
hintSoftKeyboard();
recyclerSuggest.setVisibility(View.GONE);

linearViewPager.setVisibility(View.VISIBLE);
}
});
}
},
StaticUtils.consumer);
}

private void doSearch(String query) {
webRequest.setQ(query);
webRequest.setLang(Language.ENGLISH);
webRequest.setLang(Language.FRENCH);
webRequest.setSregion(Region.INDIA);
webRequest.setPn(1);
webRequest.setPs(10);
webRequest.setWithin("www.fragrancespecialities.com");

commonRequest.setQ(query);
commonRequest.setLang(Language.ENGLISH);
commonRequest.setLang(Language.FRENCH);
commonRequest.setSregion(Region.INDIA);
commonRequest.setPn(1);
commonRequest.setPs(10);

Observable.create(StaticUtils.observable)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Consumer<BaseSearchResponse>() {
@Override
public void accept(BaseSearchResponse baseSearchResponse) throws Exception {
if (baseSearchResponse != null && baseSearchResponse.getData() != null) {
if (mCurrentPage == 0) {
WebFragment webFragment =
(WebFragment) viewPagerAdapter.getFragments().get(mCurrentPage);
webFragment.setValue((List<WebItem>) baseSearchResponse.getData());
}
}
}
},
StaticUtils.consumer);
}

private void hintSoftKeyboard() {
InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && imm.isActive() && this.getCurrentFocus() != null) {
if (this.getCurrentFocus().getWindowToken() != null) {
imm.hideSoftInputFromWindow(
this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}

public void initRetrofit() {
ApplicationInfo appInfo = null;
String baseUrl = "";
try {
appInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
baseUrl = appInfo.metaData.getString("baseUrl");
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "get meta data error: " + e.getMessage());
}
QueryService service = NetworkManager.getInstance().createService(this, baseUrl);
service.getRequestToken(
"client_credentials",
"103029525",
"6333c6fa883a91f8b0bd783d43edfb5695cb9d4612a481e2d55e6f80c2d870b")

.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TokenResponse>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(TokenResponse tokenResponse) {
if (tokenResponse != null) {
if (tokenResponse.getAccess_token() != null) {
// Log.e(TAG, response.getBody().getAccess_token());
SearchKitInstance.getInstance().setInstanceCredential(tokenResponse.getAccess_token());
} else {
Log.e(TAG, "get responseBody token is null");
}
} else {
Log.e(TAG, "get responseBody is null");
}
}

@Override
public void onError(Throwable e) {
Log.e(TAG, "get token error: " + e.getMessage());
}

@Override
public void onComplete() {

}
});
}

private static class StaticUtils {
private static class MyConsumer implements Consumer<Throwable> {
@Override
public void accept(Throwable throwable) throws Exception {
Log.e(TAG, "do search error: " + throwable.getMessage());
}
}

private static Consumer<Throwable> consumer = new MyConsumer();

private static class MyObservable implements ObservableOnSubscribe<BaseSearchResponse> {
@Override
public void subscribe(ObservableEmitter<BaseSearchResponse> emitter) throws Exception {
if (mCurrentPage == 0) {
BaseSearchResponse<List<WebItem>> webResponse =
SearchKitInstance.getInstance().getWebSearcher().search(webRequest);
emitter.onNext(webResponse);
}
}
}

private static ObservableOnSubscribe<BaseSearchResponse> observable = new MyObservable();
}
}

Tips & Tricks

The parameter in the setQ method of WebSearchRequest cannot be empty or exceed 1024 characters. (If the parameter passed exceeds 1024 characters, only the first 1024 characters will be used.) Otherwise, the search fails.

Results

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106110554.29583096860281291942093738760859:50511118054909:2800:8BA7EEA5A53E9A59C5E7881092C16F6FA38E9094A991CB0C21857B93D2EBAFCD.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106110554.29583096860281291942093738760859:50511118054909:2800:8BA7EEA5A53E9A59C5E7881092C16F6FA38E9094A991CB0C21857B93D2EBAFCD.png[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106105327.92654354495268664774479542383025:50511118054909:2800:8F229507DC09E84E2DB35B54ACA66BF474A81C42D56A96A5C837E0272E7459BF.gif"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/834/235/2640091000001834235.20201106105327.92654354495268664774479542383025:50511118054909:2800:8F229507DC09E84E2DB35B54ACA66BF474A81C42D56A96A5C837E0272E7459BF.gif[/IMG]

Reference

https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001055591730

Conclusion

This article focuses on web search for mobile application which query the website and demonstrate the client side API’s to initiate the search process, communicate with Huawei search API’s, customize the query and handle the results on the mobile application sent through the website.