Pull to refresh

Использование Android Search Dialog. Часть 2 — Recent Query Suggestions

Reading time8 min
Views5.6K
image

Статья рекомендуется к прочтению тем, кто осилил первую часть. В статье объясняется, как модифицировать ваше приложение так, чтобы к диалогу добавлялись подсказки по недавним поисковым запросам. Для понимания кода и теории (помимо той, что требовалась в первой части) требуется знание контент-провайдеров. Его можно почерпнуть из официального гайда.



Немного теории


По сути, подсказки по недавним поисковым запросам представляют собой просто сохраненные запросы. Когда пользователь выбирает одну из подсказок, то Activity, которое отвечает за поиск, получает Intent типа Search с подсказкой в качестве строки, которую оно уже обрабатывало ранее. За отображение подсказок, так же как и за весь диалог, отвечает Search Manager, а для хранения используется контент-провайдер.

Когда Search Manager определяет наше Activity как отвечающее за поиск и обеспечивающее подсказки к поиску, то происходит следующая последовательность действий:
  1. Когда Search Manager получает текст поискового запроса, то он отправляет свой запрос к контент-провайдеру, обеспечивающему подсказки.
  2. Контент-провайдер возвращает курсор, указывающий на подсказки, которые совпадают с текстом поискового запроса.
  3. Search Manager отображает подсказки, используя курсор

После того как список подсказок был отображен, может случиться следующее:
  • Если пользователь изменяет текст запроса, то все вышеперечисленные шаги повторятся.
  • Если пользователь запускает поиск, то подсказки игнорируются.
  • Если пользователь выбирает подсказку, то к Activity доставляется Intent с текстом этой подсказки в качестве запроса.

Итак, для реализации подсказок нам потребуется следующее:
  • Создать контент-провайдер, который будет наследником класса SearchRecentSuggestionsProvider и объявить его в манифесте
  • Изменить конфигурационный xml файл диалога, добавив в него информацию о контент-провайдере
  • Изменить Activty так, чтобы оно сохраняло запросы каждый раз, когда запускается поиск

Создаем контент-провайдер


Всё что от нас требуется, это контент-провайдер, который является наследником класса SearchRecentSuggestionsProvider. Этот класс практически делает за разработчика все действия и всё, что от нас требуется — это написать конструктор.

Файл SuggestionProvider.java
package com.example.search;

import android.content.SearchRecentSuggestionsProvider;

public class SuggestionProvider extends SearchRecentSuggestionsProvider {
  public final static String AUTHORITY = "com.example.search.SuggestionProvider";
  public final static int MODE = DATABASE_MODE_QUERIES;

  public SuggestionProvider() {
    setupSuggestions(AUTHORITY, MODE);
  }
}


* This source code was highlighted with Source Code Highlighter.

Методу setupSuggestions() передается строка авторизации и режим работы БД контент-провайдера. Строка авторизации может быть любой, единственное требование — уникальность. Однако, в официальной документации рекомендуется использовать полное имя контент-провайдера, включая название пакета. Режим работы БД должен включать в себя DATABASE_MODE_QUERIES, также опционально можно добавить DATABASE_MODE_2LINES. Во втором случае к таблице подсказок добавляется столбец, позволяющий отображать для каждой подсказки вторую строку. Выглядеть в коде будет так:
public final static int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;

* This source code was highlighted with Source Code Highlighter.

Теперь не забываем, что нужно объявить наш контент-провайдер в манифесте.

Файл AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.search"
   android:versionCode="1"
   android:versionName="1.0">
  <application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".Main"
         android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.SEARCH" />
      </intent-filter>
      <meta-data
        android:name="android.app.searchable"
        android:resource="@xml/searchable"
      />
    </activity>
    <provider android:name=".SuggestionProvider"
         android:authorities="com.example.search.SuggestionProvider" />    
  </application>
  <uses-sdk android:minSdkVersion="5" />

</manifest>


* This source code was highlighted with Source Code Highlighter.


Изменение конфигурационного файла



Для того, чтобы диалог использовал наш контент-провайдер для подсказок нужно добавить в него параметры android:searchSuggestAuthority и android:searchSuggestSelection

Файл searchable.xml
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
  android:label="@string/app_name"
  android:hint="@string/search_hint"
  android:searchSuggestAuthority="com.example.search.SuggestionProvider"
  android:searchSuggestSelection=" ?">
</searchable>


* This source code was highlighted with Source Code Highlighter.

Значение параметра android:searchSuggestAuthority должно полностью совпадать со строкой авторизации контент-провайдера.
Значение параметра android:searchSuggestSelection должно быть представлять знак вопроса, поставленный после пробела, потому что это аргумент выборки из БД и знак вопроса автоматически заменяется на текст, введенный пользователем.

Изменение Activity



Всё что нам нужно — это сохранить текст запроса, для этого создается экземпляр класса SearchRecentSuggestions и вызывается метод saveRecentQuery(). Это происходит каждый раз, когда в Activity приходит Intent с запросом на поиск данных. В метод saveRecentQuery() передается два параметра, первый является обязательным и представляет собой строку поискового запроса, второй — опциональный, требуется если вы используете DATABASE_MODE_2LINES для отображения второй строки текста в подсказке.

В официальной документации рекомендуется также предусмотреть интерфейс для очистки всей таблицы подсказок. По всей видимости, это нужно для обеспечения user's privacy. Мы просто добавим еще один пункт меню, при нажатии на который будет вызываться очистка всей истории запросов.

Файл Main.java
package com.example.search;

import android.app.ListActivity;
import android.app.SearchManager;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SimpleCursorAdapter;

public class Main extends ListActivity {
  private EditText text;
  private Button add;
  private RecordsDbHelper mDbHelper;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    //Создаем экземпляр БД
    mDbHelper = new RecordsDbHelper(this);
    //Открываем БД для записи
    mDbHelper.open();
    //Получаем Intent
    Intent intent = getIntent();
    //Проверяем тип Intent
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
      //Берем строку запроса из экстры
      String query = intent.getStringExtra(SearchManager.QUERY);
      //Создаем экземпляр SearchRecentSuggestions
      SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
          SuggestionProvider.AUTHORITY, SuggestionProvider.MODE);
      //Сохраняем запрос
      suggestions.saveRecentQuery(query, null);      
      //Выполняем поиск
      showResults(query);
    }

    add = (Button) findViewById(R.id.add);
    text = (EditText) findViewById(R.id.text);
    add.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
        String data = text.getText().toString();
        if (!data.equals("")) {
          saveTask(data);
          text.setText("");
        }
      }
    });
  }

  private void saveTask(String data) {
    mDbHelper.createRecord(data);
  }

  private void showResults(String query) {
    //Ищем совпадения
    Cursor cursor = mDbHelper.fetchRecordsByQuery(query);
    startManagingCursor(cursor);
    String[] from = new String[] { RecordsDbHelper.KEY_DATA };
    int[] to = new int[] { R.id.text1 };

    SimpleCursorAdapter records = new SimpleCursorAdapter(this,
        R.layout.record, cursor, from, to);
    //Обновляем адаптер
    setListAdapter(records);
  }
  //Создаем меню для вызова поиска (интерфейс в res/menu/main_menu.xml)
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_menu, menu);
    return true;  
  }

  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.search_record:
      //Вызываем поиск
      onSearchRequested();
      return true;
    case R.id.clear_recent_suggestions:
      //Очищаем историю
      SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
          SuggestionProvider.AUTHORITY, SuggestionProvider.MODE);
      suggestions.clearHistory();
      return true;
    default:
      return super.onOptionsItemSelected(item);
    }
  }
}


* This source code was highlighted with Source Code Highlighter.


Заключение

При написании статьи использовалась документация с developer.android.com
Весь измененный проект лежит на code.google.com
Tags:
Hubs:
+18
Comments8

Articles