Статьи

Интерфейс активации и шаблон Builder

Я написал два поста о процессе активации SMS . В первом я обсуждал использование Twilio API через REST, а во втором я рассмотрел нативные интерфейсы для перехвата SMS, которые мы можем использовать в Android. Теперь пришло время собрать все это воедино и создать единый API-интерфейс. Он должен включать в себя весь процесс пользовательского интерфейса, но быть достаточно гибким, чтобы позволить вам создавать свой собственный опыт.

Строитель

Я хотел создать пользовательский интерфейс, который был бы довольно типичным и стандартным, скрывая при этом много нюансов. Моей первой интуицией было просто получить Form и создать пользовательский интерфейс. Но это не очень хороший подход.

При извлечении компонента вы открываете весь API базового класса и далее, что создает непонятный API пользовательского интерфейса, поэтому я решил представить API-интерфейс построителя, который выглядит примерно так:

1
2
3
TwilioSMS smsAPI = TwilioSMS.create(accountSID, authToken, fromPhone);
ActivationForm.create("Signup").
        show(s -> Log.p(s), smsAPI);

Здесь происходит несколько вещей. Я обернул код из SMS-звонков в API-интерфейс TwilioSMS . Это позволяет мне скрыть детали отправки SMS из класса пользовательского интерфейса ActivationForm . Вы заметите, что я создаю экземпляр этого класса, используя метод create который принимает заголовок формы. Затем у меня есть метод show который перезванивает нам с номером телефона, когда активация проходит успешно…

Поскольку это шаблон строителя, вы можете настроить все виды вещей:

1
2
3
4
5
6
ActivationForm.create("Signup").
        codeDigits(5). // number of digits in the activation code sent via SMS
        enterNumberLabel(string). // text of the label above the number input
        includeFab(true). // true if a fab should be shown, by default a fab button will appear in Android only
        includeTitleBarNext(true). // true if a next arrow should appear in the title, by default this would appear in non-Android platforms
        show(s -> Log.p(s), smsAPI);

Замечательно то, что вы не можете делать все то, чего не должны делать, поскольку класс ActivationForm производным от Object и имеет собственный конструктор.

Пользовательский интерфейс для формы активации показывает форму поверх текущей формы с пользовательским интерфейсом ввода номера.

Рисунок 1. Активация SMS UI / UX

Рисунок 2. При нажатии на код страны вам предлагается список стран

Получение списка стран

Я получил список флагов и стран с https://mledoze.github.io/countries/

Я преобразовал флаги SVG в PNG и добавил их все в файл flags.res . Это более эффективно, чем открытие нескольких png-файлов из системы. Я подумал об использовании файлов SVG с нашим транскодером, но решил отказаться от него из-за незрелости и возможных проблем с производительностью инструмента. Флаги — чувствительные вещи, и ошибка в транскодере может означать, что мы делаем что-то оскорбительное.

Я не мог пропустить флаги, поскольку они предоставляют визуальный элемент, который полностью меняет восприятие пользовательского интерфейса.

Я подумал о том, чтобы отправить приложение с файлом данных JSON, но оно содержит много ненужной мне информации и весит 500 КБ. Поэтому я написал быстрое приложение, которое просто распечатывало данные в виде массивов, и вставил их в исходный файл. Это важный шаг, так как список может измениться, и нам нужно пройти его снова … Вот как я проанализировал JSON, я просто запустил это в симуляторе и вставил вывод в исходный файл Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
private static final CaseInsensitiveOrder cio = new CaseInsensitiveOrder();
class Country implements Comparable<Country> {
    public final String name;
    public final String code;
    public final String flag;
    public final String isoCode2;
    public final String isoCode3;
    public Country(String name, String code, String flag, String isoCode2, String isoCode3) {
        this.name = name;
        this.code = code;
        this.flag = flag;
        this.isoCode2 = isoCode2;
        this.isoCode3 = isoCode3;
    }
    @Override
    public int compareTo(Country o) {
        return cio.compare(name, o.name);
    }
}
ArrayList<Country> con = new ArrayList<>();
try {
    JSONParser p = new JSONParser();
    Map<String, Object> dat = p.parseJSON(new InputStreamReader(getResourceAsStream("/countries.json"))); (1)
    List<Map<String, Object>> l = (List<Map<String, Object>>)dat.get("root");
    for(Map<String, Object> m : l) { (2)
        List ll = ((List)m.get("callingCode"));
        if(ll != null && ll.size() > 0) {
            String name = (String)((Map)m.get("name")).get("common");
            String callingCode = (String)ll.get(0);
            String code = (String)m.get("cioc");
            String flag = null;
            if(code != null && code.length() > 0) {
                flag = code.toLowerCase();
            }
            String isoCode2 = (String)m.get("cca2");
            String isoCode3 = (String)m.get("cca3");
            con.add(new Country(name, callingCode, flag, isoCode2, isoCode3)); (3)
        }
    }
} catch(IOException err) {
    Log.e(err);
}
Collections.sort(con); (4)
System.out.println("private static final String[] COUNTRY_NAMES = {");
for(Country c : con) { (5)
    System.out.println("    \"" + c.name + "\",");
}
System.out.println("};");
System.out.println("private static final String[] COUNTRY_CODES= {");
for(Country c : con) {
    System.out.println("    \"" + c.code + "\",");
}
System.out.println("};");
System.out.println("private static final String[] COUNTRY_FLAGS = {");
for(Country c : con) {
    if(c.flag == null) {
        System.out.println("    null,");
    } else {
        System.out.println("    \"" + c.flag + "\",");
    }
}
System.out.println("};");
System.out.println("private static final String[] COUNTRY_ISO2 = {");
for(Country c : con) {
    System.out.println("    \"" + c.isoCode2 + "\",");
}
System.out.println("};");
System.out.println("private static final String[] COUNTRY_ISO3 = {");
for(Country c : con) {
    System.out.println("    \"" + c.isoCode3 + "\",");
}
System.out.println("};");

Большая часть этого кода довольно проста:

1 Я загружаю файл JSON из ресурсов
2 Я перебираю записи в файле JSON по одной
3 Я Country объект Country на основе данных ввода
4 Country сортируется благодаря интерфейсу Comparable поэтому я могу просто отсортировать список
5 Теперь я могу просто создать 5 массивов строк, которые я хочу

Да, я знаю, что мог бы сохранить объект Country и работать с ним вместо 5-ти строковых массивов … Я мог бы изменить это в будущем.

Упаковка в CN1LIB

Бриллиант удивил меня пару недель назад, когда он упаковал мой последний пост как cn1lib . Я думаю, это здорово, если вам нужен перехват SMS-сообщений.

Этот cn1lib сильно отличается по своим целям. Я хотел решить очень конкретный и ограниченный вариант использования проверки пользователя с помощью SMS. Поэтому, хотя у нас есть некоторые общие черты с нашими cn1libs, основная предпосылка довольно далека и это видно.

Смотрите оригинальную статью здесь: СОВЕТ: Интерфейс активации и шаблон Builder

Мнения, высказанные участниками Java Code Geeks, являются их собственными.