Статьи

Создание приложения чата с кодовым названием One Part 6 — Codename One

Это будет последняя часть этого урока, которая уже была довольно завершена в предыдущем разделе. У нас могут быть дополнительные взносы, в основном для покрытия улучшений, таких как «пригласить друга» и другие подобные возможности, но это, вероятно, такое же хорошее место, как и любое другое, чтобы закончить приложение и позволить вам попробовать его вживую.

Родной толчок

До сих пор мы использовали PubNub для реализации функциональности push, что отлично, особенно если вы выбираете платную опцию, которая также может сохранять сообщения и предлагает довольно много дополнительных привилегий для такого приложения, как это. Тем не менее, когда приложение не работает, PubNub не может что-либо выдвинуть, и в этом случае для отправки сообщения нам понадобится собственное нажатие ОС.

Очевидный вопрос: «Почему бы не использовать встроенную поддержку ОС для всего?».

Это возможно, но встроенная поддержка ОС не такая гибкая, портативная, быстрая и надежная, как PubNub. С ним больно работать, и пользователь может преднамеренно или непреднамеренно прервать его, не согласившись с приглашением операционной системы и т. Д. Как вы увидите из оставшейся части учебника, где мы возвращаемся к принудительной загрузке собственной ОС, когда PubNub не может достичь нашей цели, ее нет панацеи

Интерфейс PushCallback

Мы начнем с реализации интерфейса PushCallback в нашем основном классе, это должен быть фактический основной класс, иначе push не будет работать:

1
public class SocialChat implements PushCallback {

Затем нам нужно реализовать следующие методы:

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
@Override
public void push(String value) {
    // its a JSON message, otherwise its a notice to the user
    if(value.startsWith("{") || value.startsWith("[")) {
        try {
            JSONObject obj = new JSONObject(value);
 
            // this is still early since we probably didn't login yet so add the messages to the list of pending messages
            java.util.List<Message> pendingMessages = (java.util.List<Message>)Storage.getInstance().readObject("pendingMessages");
            if(pendingMessages == null) {
                pendingMessages = new ArrayList<>();
            }
            Message m = new Message(obj);
            pendingMessages.add(m);
            Storage.getInstance().writeObject("pendingMessages", pendingMessages);
            addMessage(m);
        } catch(JSONException err) {
            err.printStackTrace();
        }
    }
}
 
@Override
public void registeredForPush(String deviceId) {
}
 
@Override
public void pushRegistrationError(String error, int errorCode) {
}

Вы заметите следующие вещи здесь:

  • Нам ничего не нужно в registeredForPush pushRegistrationError или pushRegistrationError . Поскольку мы используем PubNub, даже если push не работает, приложение все равно будет работать нормально. Push.getPushKey() обычно используется для получения нажимной клавиши (которая не является аргументом, переданным этому методу, это Push.getPushKey() ) и отправляет его на ваши серверы, чтобы вы могли инициировать нажатие на это устройство. Так как здесь у нас нет реального сервера, мы не используем этот метод.
  • Метод push делает тяжелую обработку push-сообщений. Он может быть вызван в любое время и принимает обратные вызовы. Он получает как видимые, так и скрытые push-сообщения и решает, что с ними делать, основываясь на их содержимом.
  • Мы не показываем сообщение во время обратного вызова. Он будет вызван до того, как пользователь успел войти в систему, поэтому мы хотим просто сохранить объекты Message и обработать их позже. Мы по-прежнему добавляем их в общий магазин на тот случай, если пользователь решит убить приложение, прежде чем войти в систему.

Новые константы и регистрация

Нам нужно добавить следующие переменные в класс, чтобы продолжить, я замаскировал и изменил значения. Мы рассмотрим их один за другим:

1
2
3
4
5
6
7
8
private static final String PUSH_TOKEN = "********-****-****-****-*************";
private static final String GCM_SENDER_ID = "99999999999999";
private static final String GCM_SERVER_API_KEY = "******************-********************";
private static final boolean ITUNES_PRODUCTION_PUSH = false;
private static final String ITUNES_PRODUCTION_PUSH_CERT = "https://domain.com/linkToP12Prod.p12";
private static final String ITUNES_PRODUCTION_PUSH_CERT_PASSWORD = "ProdPassword";
private static final String ITUNES_DEVELOPMENT_PUSH_CERT = "https://domain.com/linkToP12Dev.p12";
private static final String ITUNES_DEVELOPMENT_PUSH_CERT_PASSWORD = "DevPassword";

PUSH_TOKEN является самым простым, просто войдите в Codename One и выберите вкладку учетной записи. Он должен появиться прямо над кнопкой « Update Details . Если его там нет, попробуйте выйти и снова войти в систему.

Вы можете очень легко получить GCM_SENDER_ID & GCM_SERVER_API_KEY от Google. Это предполагает, что вы следовали нашим инструкциям по созданию проекта Google во второй части руководства. Если вы пропустили это (поскольку вам не требовалось входить в учетную запись G +), просто создайте новый проект в консоли API Google на основе инструкций в части 2.

Чтобы сгенерировать их, просто перейдите на https://developers.google.com/mobile/add и нажмите «выбрать платформу»:

чат-приложение-учебник-GCM-1

Выберите «Android App»

чат-приложение-учебник-GCM-2

Введите данные для приложения и пакета

чат-приложение-учебник-GCM-3

Нажмите «Облачные сообщения», затем нажмите «Включить Google Cloud Messaging».

чат-приложение-учебник-GCM-4

Теперь у вас должны быть значения для GCM_SENDER_ID и GCM_SERVER_API_KEY как показано ниже

чат-приложение-учебник-GCM-5

Благодаря нашему новому мастеру сертификатов генерация части этих флагов для iOS теперь совершенно легкая!

Мы просто пройдем через мастер сертификатов и отметим флаг, чтобы включить push:

Включить толчок мастера

После завершения работы мастера и проверки флажка включения push убедитесь, что в разделе iOS также установлен флажок «Включить push». В текущем плагине есть ошибка, когда он не включается автоматически.

Вы должны получить инструкции по отправке по электронной почте, которые должны содержать ссылки, которые вы можете просто вставить на место, и пароли. Это должно быть довольно легко.

Другие изменения кода

Когда вы нажимаете на устройство, вам нужно иметь ключ устройства, который является уникальным идентификатором устройства, на которое вы хотите отправить push. К сожалению, поскольку у нас нет сервера, нам нужно как-то передать этот ключ от человека, с которым мы общаемся. Хитрость заключается в том, чтобы встроить этот ключ в объект Message и, таким образом, обновить его при получении сообщения. Это означает, что мы можем отправить push-сообщение только тому человеку, который писал нам в прошлом. Неплохая особенность всего и вся, но все же ограничение …

Для этого нам нужно сделать два простых изменения в классе Message :

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
public Message(JSONObject obj) {
    try {
        time = Long.parseLong(obj.getString("time"));
        senderId = obj.getString("fromId");
        recepientId = obj.getString("toId");
        message = obj.getString("message");
        name = obj.getString("name");
        picture = obj.getString("pic");
 
        // update the push id for the given user
        if(obj.has("pushId")) {
            String pushId = obj.getString("pushId");
            if(pushId != null) {
                Preferences.set("pid-" + senderId, pushId);
            }
        }
    } catch (JSONException ex) {
        // will this ever happen?
        Log.e(ex);
    }
}
 
public JSONObject toJSON() {
    String pushId = Push.getPushKey();
    if(pushId != null) {
        JSONObject obj = createJSONObject("fromId", senderId,
                "toId", recepientId,
                "name", name,
                "pic", picture,
                "time", Long.toString(System.currentTimeMillis()),
                "message", message, "pushId", pushId);
        return obj;
    }
    JSONObject obj = createJSONObject("fromId", senderId,
            "toId", recepientId,
            "name", name,
            "pic", picture,
            "time", Long.toString(System.currentTimeMillis()),
            "message", message);
    return obj;
}

Это эффективно добавляет push-идентификатор к каждому отправляемому нами сообщению, если оно доступно, и обновляет push-идентификатор контактов для использования позже.

Теперь нам нужно зарегистрироваться для push, в конце метода start() в SocialChat.java добавим:

1
2
3
4
5
6
7
8
// let the login form show before we register the push so the permission screen doesn't appear on a white
// background
Display.getInstance().callSerially(() -> {
    // registering for push after the UI appears
    Hashtable args = new Hashtable();
    args.put(com.codename1.push.Push.GOOGLE_PUSH_KEY, GCM_SENDER_ID);
    Display.getInstance().registerPush(args, true);
});

Мы делаем это таким образом, чтобы пользовательский интерфейс появился первым.

Ранее в методе showChatForm мы только что отправили сообщение через PubNub, теперь мы хотим, чтобы был запасной вариант, который отправит сообщение с помощью push. Для этого нам нужно знать, что сообщение не было получено другой стороной. Чтобы обнаружить, что теперь мы добавляем в PubNub ответное сообщение под названием «ACK», которое будет подтверждать получение сообщения, если ACK не получен, это означает, что сообщение должно быть отправлено с помощью собственного push… Для этого мы добавляем класс поле:

1
2
3
4
/**
 * Includes messages that received ACK notices from the receiver
 */
private ArrayList<String> pendingAck = new ArrayList<>();

Мы автоматически удаляем ACK в методе listenToMessages :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void listenToMessages() {
    try {
        pb = new Pubnub("pub------------------------------", "sub-------------------------------");
        pb.subscribe(tokenPrefix + uniqueId, new Callback() {
            @Override
            public void successCallback(String channel, Object message, String timetoken) {
                if(message instanceof String) {
                    pendingAck.remove(channel);
                    return;
                }
                Message m = new Message((JSONObject)message);
                pb.publish(tokenPrefix + m.getSenderId(),  "ACK", new Callback() {});
                Display.getInstance().callSerially(() -> {
                    addMessage(m);
                    respond(m);
                });
            }
        });
    } catch(PubnubException err) {
        Log.e(err);
        Dialog.show("Error", "There was a communication error: " + err, "OK", null);
    }
}

В методе showChatForm нам нужно использовать showChatForm к push, это немного большой метод, поэтому я showChatForm здесь только соответствующий раздел:

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
final Message messageObject = new Message(tokenPrefix + uniqueId, tokenPrefix + d.uniqueId, imageURL, fullName, text);
JSONObject obj = messageObject.toJSON();
 
String pid = Preferences.get("pid-" + tokenPrefix + d.uniqueId, null);
if(pid != null) {
    // if we have a push address for the contact we can send them a push if they aren't reachable...
    UITimer timeout = new UITimer(() -> {
        if(pendingAck.contains(tokenPrefix + d.uniqueId)) {
            pendingAck.remove(tokenPrefix + d.uniqueId);
            // send two messages, one hidden with the data as JSON for parsing on the client
            // the other one visible with the text that should appear to the user who isn't running
            // the app, this will allow him to launch the app and then receive the hidden message immediately
            // within the app
            String cert = ITUNES_DEVELOPMENT_PUSH_CERT;
            String pass = ITUNES_DEVELOPMENT_PUSH_CERT_PASSWORD;
            if(ITUNES_PRODUCTION_PUSH) {
                cert = ITUNES_PRODUCTION_PUSH_CERT;
                pass = ITUNES_PRODUCTION_PUSH_CERT_PASSWORD;
            }
            if(Push.sendPushMessage(PUSH_TOKEN, text + ";" + obj.toString(),
                    ITUNES_PRODUCTION_PUSH, GCM_SERVER_API_KEY, cert, pass, 3, pid)) {
                t.getUnselectedStyle().setOpacity(255);
                t.repaint();
                addMessage(messageObject);
            } else {
                chatArea.removeComponent(t);
                chatArea.revalidate();
                Dialog.show("Error", "We couldn't reach " + d.name + " thru push", "OK", null);
            }
        }
    });
 
    timeout.schedule(10000, false, write.getComponentForm());
    if(!pendingAck.contains(tokenPrefix + d.uniqueId)) {
        pendingAck.add(tokenPrefix + d.uniqueId);
    }
}

Как это работает, довольно просто:

  1. Если у нас есть идентификатор push, то мы создаем 10 секундный таймер
  2. Когда таймер истекает, мы проверяем, находится ли pendingAck в ожидании, если это так, мы должны отступить, чтобы нажать
  3. У нас есть кнопка устройства из класса Message выше, поэтому отправка push-сообщения довольно проста относительно
  4. Мы отправляем push типа 3, который включает в себя как видимую, так и невидимую полезную нагрузку, разделенную двоеточием (;). Видимая полезная нагрузка — это просто текст сообщения, тогда как невидимая полезная нагрузка — это строка JSON, которую мы хотим добавить в базу данных сообщений.

И это в значительной степени это для приложения чата!

Последнее слово

Когда я начинал с этого урока, я еще не был знаком с интеграцией Parse для Codename One. После того, как я немного поиграл с ним и был бы поражен этим, я бы спроектировал все это приложение поверх него и немного упростил процесс. Это также позволило бы мне отслеживать сохраненные сообщения push ID и устранять некоторые проблемы, возникающие при переходе между PubNub / native push.

Я бы все еще использовал PubNub без сомнения! Это удивительно и очень удобно для быстрой пуш-сети. Я думаю, что объединение с Parse сделало бы это приложение намного лучше.

Вход в систему через Google / Facebook и т. Д. Был, вероятно, самой болезненной частью приложения, и я включаю push-уведомление в набор трудностей. Хотя это намного проще, чем раньше, и проще, чем нативная / веб-версии, я думаю, что основная проблема заключается в непрозрачности сетей и желании держать разработчиков близко. Боль меньше с нашей стороны и больше от утомительного создания приложений и передачи значений в Facebook / Google. Ключ хэша APK просто болезненный, были такие вещи, как «пригласить друга», которых я просто избегал из-за скуки.

Я мог бы сделать переписку с учетом этих мыслей, но я более склонен повторить это как cn1lib, а не как приложение. Основной мотивацией является поддержка приложений конечными пользователями, поэтому разработчики могут общаться с пользователями по вопросам, интегрируя в наше приложение одну cn1lib. Я не уверен, что у меня будет время разобраться с чем-то подобным, но я думаю, что это должно быть относительно легко, так как большая часть больших ресурсов (push, облачное хранилище и т. Д.) Уже обрабатывается этими замечательными сторонними сервисами.

Другие сообщения в этой серии

Это непрерывная серия постов, включающая следующие части: