• WebView - как перезагрузить POST страницу?

    akaish
    @akaish
    Не уверен, но м.б. попробовать следующее: добавить к странице эквайринга свой js скрипт через WV, связать его с нативным кодом, добавить на кнопку оплаты хук, вызывающий код добавленного скрипта, который, в свою очередь, возьмет данные для POST и передаст нативной части приложения, потом, при ошибке - просто сгенерировать новый POST на основе данных, полученных через хук и отправить его. Но это костыль.
    Написано
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    Хотя, если честно, даже не копайтесь, это уже мои проблемы :) Просто интересно, как сделать очередь с генерацией команд для исполнения налету. Вы уже вполне себе помогли, спасибо.
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    В общем, то, что хочу сделать - пока только смотрю и прикидываю, появилось время заняться рефакторингом.
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    Конкретная реализация команды для ble
    spoiler

    public class BLEDeviceCommand extends Command {
    
        private final String serviceCharacteristic;
        protected OnPreExecute onPreExecute;
    
        private byte[] writeDataIO;
        private final NotificationProcessor notificationProcessor;
    
        protected CommandResult preExecuteCommandResult;
        public interface OnPreExecute {
            CommandResult onPreExecute(@NonNull List<CommandResult> results, int commandIndex, CommandQue que);
        }
    
        public interface NotificationProcessor {
            CommandResult process(BLEDeviceCommandQue que, Object notificationObject, int idx, int onSuccess);
        }
    
    
        public BLEDeviceCommand(@NonNull BLEDeviceCommandQue commandQue, int type, @Nullable Long executionTimeout,
                         String serviceCharacteristic, @NonNull OnPreExecute onPreExecute,
                         @Nullable NotificationProcessor notificationProcessor, int onExecutionSuccess) {
            super(commandQue, type, executionTimeout, onExecutionSuccess);
            this.serviceCharacteristic = serviceCharacteristic;
            this.onPreExecute = onPreExecute;
            this.notificationProcessor = notificationProcessor;
        }
    
    
        @Override
        public @Nullable Object createMessage(Object object) {
            return null;
        }
    
        @Override
        public CommandResult onPreExecute(@NonNull List<CommandResult> results, int commandIndex, CommandQue que) {
            preExecuteCommandResult = onPreExecute.onPreExecute(results, commandIndex, que);
            if(type == COMMAND_TYPE_WRITE)
                writeDataIO = preExecuteCommandResult.data;
            return preExecuteCommandResult;
        }
    
        @Override
        public void executeRequest(OnExecutedIO OnExecutedIO) {
            if(type == COMMAND_TYPE_WRITE) {
                ((BLEDeviceCommandQue) commandQue).masterDeviceManager.getBLEManager().writeData(
                        writeDataIO,
                        serviceCharacteristic,
                        (request) -> {
                            Object obj = createMessage(request);
                            if(obj != null)
                                message = new CommandMessage(commandIndex, obj);
                            OnExecutedIO.onExecution(
                                    new CommandResultBuilder().setQueId(commandQue.queId)
                                            .setOperationIndex(commandIndex)
                                            .setData(writeDataIO)
                                            .setCode(onExecutionSuccess)
                                            .build());
                        }
                        ,
                        (request, bleErrCode, tr) -> OnExecutedIO.onExecution(
                                new CommandResultBuilder().setQueId(commandQue.queId)
                                        .setOperationIndex(commandIndex)
                                        .setCode(CommandResult.IO_ERROR)
                                        .setIoCode(bleErrCode)
                                        .setException(tr)
                                        .build()),
                        23 // TODO 1 MTU
    
                );
                return;
            }
            if(type == COMMAND_TYPE_READ) {
                ((BLEDeviceCommandQue) commandQue).masterDeviceManager.getBLEManager().readData(
                        new DeviceIOEvents.READ_REQUEST(...),
                        (response) -> {
                            Object obj = createMessage(response);
                            if(obj != null)
                                message = new CommandMessage(commandIndex, obj);
                            OnExecutedIO.onExecution(
                                    new CommandResultBuilder().setQueId(commandQue.queId)
                                            .setOperationIndex(commandIndex)
                                            .setData(response.toByteArray())
                                            .setCode(onExecutionSuccess)
                                            .build());
                        },
                        (response, bleErrCode, tr) -> OnExecutedIO.onExecution(
                                new CommandResultBuilder().setQueId(commandQue.queId)
                                        .setOperationIndex(commandIndex)
                                        .setCode(CommandResult.IO_ERROR)
                                        .setIoCode(bleErrCode)
                                        .setException(tr)
                                        .build())
                );
            }
        }
    
        @Override
        public CommandResult onWaitComplete() {
            return new CommandResultBuilder().ююю.build();
        }
    
        @Nullable
        @Override
        public CommandResult onNotificationReceived(Object notification) {
            if(type != COMMAND_TYPE_WAIT_FOR_NOTIFICATION)
                return null;
            if(notificationProcessor != null)
                return notificationProcessor.process((BLEDeviceCommandQue) commandQue, notification, commandIndex, onExecutionSuccess);
            return null;
        }
    }



    Очередь запускается в отдельном потоке, обрабатывает весь этот список команд, собирает по списку результатов выполнения один объект и потом EventBus'ом отправляет его заинтересованным листенерам. Вообще, работает ок, но переусложнено и не допускает создание очереди налету. Хочу из куска легаси переработать в что-то удобоваримое и читаемое.
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    Уффф, ну ок
    Сейчас работаю с очередями запросов в отдельном потоке, сначала ручками генерирую IO очередь
    Сама очередь:
    spoiler

    public class CommandQue {
    
        private final LinkedList<Command> commands = new LinkedList<>();
        protected final ArrayList<CommandResult> results = new ArrayList<>();
        private Command currentCommand;
        private final Handler timeoutHandler = new Handler();
        private TimeoutRunnable timeoutRunnable;
        private DeviceOperationListener operationListener;
        private final LinkedList<CommandMessage> messages = new LinkedList<>();
    
        public CommandQue(long queId, int quePriority, final String operationName, int operationType, int operationId) {
            this.queId = queId;
            this.quePriority = quePriority;
            this.operationName = operationName;
            this.operationType = operationType;
            this.operationId = operationId;
        }
    
        public final int getOperationId() {
            return operationId;
        }
    
        public void setOperationListener(DeviceOperationListener deviceOperationListener) {
            this.operationListener = deviceOperationListener;
        }
    
        public void setOnQueFinish(@NonNull final OnQueFinish onQueFinish) {
            this.onQueFinish = onQueFinish;
        }
    
    
        public void append(Command command) {
            command.setCommandIndex(lastCommandIndex);
            commands.addLast(command);
            lastCommandIndex++;
        }
    
        void appendCommandMessage(@Nullable CommandMessage message) {
            if(message != null)
                messages.addLast(message);
        }
    
        public @Nullable CommandMessage getLastCommandMessage() {
            if(messages.size() > 0)
                return messages.getLast();
            else return null;
        }
    
        public LinkedList<CommandMessage> getCommandMessages() {
            return messages;
        }
    
        void appendExecutionResult(CommandResult result) {
            if(timeoutRunnable != null)
                timeoutHandler.removeCallbacks(timeoutRunnable);
            if(finished) return;
            resultCounter++;
            results.add(resultCounter, result);
            if(operationListener != null) {
                DeviceOperationBuilder builder = new DeviceOperationBuilder();
                builder.setCurrentOperation(result.operationIndex)
                        .setDevice(getDevice())
                        .setOperationName(getOperationName())
                        .setOverallOperationsCount(commands.size())
                        .setResult(result)
                        .setQueId(queId)
                        .setOperationType(operationType)
                        .setPriorityGroup(quePriority);
                operationListener.listen(builder.build());
            }
            if(result.code > 0) {
                finished = true;
                onQueFinish.onQueFinished(results);
            }
        }
    
        public void cancelQueue() {
            appendExecutionResult(new CommandResultBuilder().setQueId(queId)
                    .setOperationIndex(-1)
                    .setCode(QUE_FINISHED_BY_REQUEST)
                    .setErrMessage("Que finished manually")
                    .build());
        }
    
        int size = -1;
    
        public void nextCommand() {
            if(size == -1) size = commands.size();
            if(timeoutRunnable != null)
                timeoutHandler.removeCallbacks(timeoutRunnable);
            currentTask++;
            if(currentTask == size) {
                appendExecutionResult(new CommandResultBuilder().setQueId(queId)
                        .setOperationIndex(0)
                        .setCode(CommandResult.QUE_FINISHED_SUCCESSFULLY)
                        .setErrMessage("No commands left in que")
                        .build());
                return;
            }
            currentCommand = commands.get(currentTask);
            CommandResult validationResult = currentCommand.onPreExecute(results, currentCommand.commandIndex, this);
            if(validationResult.code > 0)
                onQueFinish.onQueFinished(results);
            switch (currentCommand.type) {
                case COMMAND_TYPE_READ:
                    currentCommand.execute();
                    break;
                case COMMAND_TYPE_WRITE:
                    currentCommand.execute();
                    break;
                case COMMAND_TYPE_WAIT_FOR_NOTIFICATION:
                    break;
                case COMMAND_TYPE_WAIT:
                    break;
                    default:
                        appendExecutionResult(
                                new CommandResultBuilder()
                                        .setQueId(queId)
                                        .setOperationIndex(currentCommand.commandIndex)
                                        .setCode(INTERNAL_ERROR)
                                        .setErrMessage("Unknown command type")
                                        .build()
                        );
    
            }
            if(currentCommand.executionTimeout != null) {
                timeoutRunnable = new TimeoutRunnable(currentCommand);
                timeoutHandler.postDelayed(timeoutRunnable, currentCommand.executionTimeout);
            }
        }
    
        public void notificationChannelIO(Object notificationObject) {
            CommandResult result = currentCommand.onNotificationReceived(notificationObject);
            if(result == null) return; // Skipping notification
            appendExecutionResult(result);
            if(result.code == OK_READY_FOR_NEXT)
                nextCommand();
        }
    
        private static CommandResult generateTimeoutResult(long queId, int commandId) {
            ...
        }
    
        public interface OnQueFinish {
            void onQueFinished(ArrayList<CommandResult> results);
        }
    
        class TimeoutRunnable implements Runnable {
    
            final Command command;
    
            TimeoutRunnable(Command command) {
                this.command = command;
            }
    
            @Override
            public void run() {
                if(command.type == COMMAND_TYPE_WAIT) {
                    CommandResult result = command.onWaitComplete();
                    if(result == null) return;
                    appendExecutionResult(command.onWaitComplete());
                    if(result.code == OK_READY_FOR_NEXT) {
                        nextCommand();
                    }
                } else {
                    appendExecutionResult(generateTimeoutResult(queId, command.commandIndex));
                }
            }
        }
    }



    Команда в очереди
    spoiler

    public abstract class Command {
    
        protected final CommandQue commandQue;
        protected final int type;
        final Long executionTimeout;
        protected int commandIndex = -1;
        protected final int onExecutionSuccess;
    
        public Command(@NonNull CommandQue commandQue, int type, @Nullable Long executionTimeout, int onExecutionSuccess) {
            this.commandQue = commandQue;
            this.type = type;
            this.executionTimeout = executionTimeout;
            this.onExecutionSuccess = onExecutionSuccess;
        }
    
        public final static int COMMAND_TYPE_READ = 1;
        public final static int COMMAND_TYPE_WRITE = 2;
        public final static int COMMAND_TYPE_WAIT_FOR_NOTIFICATION = 3;
        public final static int COMMAND_TYPE_WAIT = 4;
    
        void setCommandIndex(int index) {
            this.commandIndex = index;
        }
    
        public interface OnExecutedIO {
            void onExecution(CommandResult result);
        }
    
        protected CommandMessage message;
        public abstract @Nullable Object createMessage(Object object);
        abstract public CommandResult onPreExecute(@NonNull List<CommandResult> results, int commandIndex, CommandQue que);
    
        void execute() {
            executeRequest((CommandResult result) -> {
                    commandQue.appendExecutionResult(result);
                    commandQue.appendCommandMessage(message);
                    commandQue.nextCommand();
                }
            );
        }
        abstract public void executeRequest(OnExecutedIO OnExecutedIO);
        abstract public @Nullable CommandResult onNotificationReceived(Object notification);
        abstract public @Nullable CommandResult onWaitComplete();
    }

  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    tiroman, смотрел, да, матчасти у меня нет, это даже не скрываю. А можете подсказать что-то развернутое и с примерами по RxJava2? Просто очень многое, что есть - слишком обрывочно и не уходит дальше
    Observable
            .from(new String[]{"1", "2", "3", "4", "5", "6"})
            .map(stringToInteger);

    а ковырять, к примеру, RxBle и другой Open Source нет желания, пока не разберусь более менее, что из чего растет.
    На vogella неплохой стартовый туториал, как по мне, на медиуме есть подборка статей. Но примеры из разряда helloworld. Как по мне, все-равно надо передавать именно команды емиттером, потом их выполнять и на основе выполнения передавать в исходный Observable с эмиттером новые команды. Но, на самом деле, пока смутно понимаю, что и как делается.
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    Т.е. сделать класс вида
    class NotificationSingleOnSubscribe implements SingleOnSubscribe, SomeListener {
         int previous;
    
         NotificationSingleOnSubscribe(int previous) {
               this.previous = previous;
         }
    
         void onNotificationReceived(Notif notification) {
              if(notification.isOk() && previous == someValueWaitForNotification)
                   emitter.onSuccess(someGoodResult);
         }
    
         void subscribe() {
               if(previous == caseOne) {
                    // Do something for case one
               } else if (previous == someValueWaitForNotification) {
                    // Wait for notification received by onNotificationReceived (do nothing) 
                   Handler h = new Handler();
                   h.postDelayed(()->emitter.onSuccess(timeoutCode), 1000L);
               }
         }
    }


    А как сгенерировать такую последовательность синглов на лету? Пример: приходит уведомление от характеристики с типом данных на устройстве, в зависимости от типа надо запросить n раз выдать такой-то кусок дампа и получить его по уведомлению. То есть размерность цепочек непостоянна, также, как типы запросов к устройству. И не хотелось бы задавать для каждого типа данных на ключе свои заранее определенные последовательности из синглов, это нерационально и не является хорошим решением в стиле Rx, по идее.
  • Как организовать синхронную последовательность команд на колбеках с RxJava2?

    akaish
    @akaish Автор вопроса
    Если честно, не совсем понятно, как в теле эмиттера дождаться колбека от листенера, к примеру, уведомлений от характеристики (или любого другого листенера). Допустим, для таймаута можно использовать каким-то образом Maybe и таймер. Ну и потом, протокольные ветвления достаточно большие, и часто приходится использовать цепочку от 2 до 10-30 последовательных команд, которые должны генерироваться на лету. Мне все-таки кажется, что надо отталкиваться от PublishSubject, каким-то образом обработать задачу, как ивент, по результатам которой заставить PublishSubject выдать ту или иную задачу в зависимости от результатов исполнения предыдущих.
  • View.OnLongClickListener зачем он возвращает boolean?

    akaish
    @akaish
    Neonoviiwolf, true if the callback consumed the long click, false otherwise
    Вольное объяснение, если true - то событие обработано и более не будет передаваться другим View далее по списку вложенности, чтобы другие листенеры могли его обработать. Там же написано.
  • Насколько плохо вызывать метод активити из фрагмента напрямую?

    akaish
    @akaish
    orbit070, на вскидку пара ситуаций: вызов метода getActivity() в методах onCreate() и onCreateView() при перезапуске активити, к примеру, при смене ориентации экрана, иногда бывает, что вызов onActivityCreated() активити происходит позже создания фрагмента и отработки методов onCreate() и onCreateView(), вторая частая ситуация - вызов этого метода когда фрагмент откреплен (detach). К примеру, у вас во фрагменте есть location listener, который вы не погасили. Фрагмент существует, получает данные геопозиции (или что угодно другое), но уже откреплен и ссылку на активити не отдаст. Вообще, документация скупая, в офф. документации сказано, что getActivity() может выдать null в случаях, когда фрагмент ассоциирован с контекстом, а не с активити.
  • Какую либу для сканирования штрих-кодов вы посоветуете?

    akaish
    @akaish
    terminator-light, из open source - не могу подсказать. Сам обычно zxing использую, для моих задач достаточно. А так выбор не велик. ML KIT при использовании на устройстве без облака - бесплатен, но его не пробовал.
  • Какую либу для сканирования штрих-кодов вы посоветуете?

    akaish
    @akaish
    terminator-light, демку их посмотрите. А так, да, можно конечно, как минимум, всегда можно клонировать репозиторий и что-то переписать под себя. Ну zxing не панацея. Инвертированный Data Matrix, к примеру, он распознает ну очень плохо
  • Где можно купить готовые приложения IOS Android с историей?

    akaish
    @akaish
    Сергей, а сколько вообще продают готовых приложений в год? Я не думаю, что много. Обычно - одно качественное и сложное приложение на продажу - одна банкротящаяся фирма или от месяца до пары лет работы инди. Кстати, а что за приложение Вам нужно? Любое? Если любое - могу продать живую обоину под андроид, если есть желание - предлагайте ценник.
  • Как сохранить весь stack фрагметов в рамках активити?

    akaish
    @akaish
    kirawa, Денис прав
    И да, в Вашем коде Вы не инициализируете интерфейс, а назначаете значение переменной, указывая ожидаемый тип данных. Но не суть.
    У Вас в ошибке указано, что StatisticsFragment не может быть приведен к типу TasksFragment$Callbacks
    Из этого я вангую то, что mCallbacks имеет тип TasksFragment$Callbacks, указанный ранее при инициализации mCallbacks. И вы пытаетесь назначить на переменную, имеющую тип, реализующий интерфейс ICallbacks, значение с приведением типа к интерфейсу, что уже само по себе является ошибкой.
    Но это уже пошло решение каких-то загадок. Ну и уж на то пошло, мне не понятна Ваша фраза:
    значит в contentMain совсем другое

    R.id.contentMain - это всего-лишь int заданный через ресурсы и сгенерированный в файл R, значение данной переменной после компиляции меняться не может.
    Ну и Вы сами в onCreate назначаете в менеджере фрагментов на id contentMain экземпляр StatisticsFragment, который не является потомком TasksFragment$Callbacks.
  • Стоит ли идти в программисты в 30 лет, но не простым, а..?

    akaish
    @akaish
    Олег Муравейко, Я Вас огорчу, но у Вас слишком узкоспециализированная ниша. В 11-13 годах владел половиной недонторки, получал доход с такой глупой мелочи, как темы оформления для android. Мой личный доход составлял на первых порах чувствительно более senior'a Java по РФ (и при этом был проект, который не дотянул, на котором работали наемные программисты + бухгалтер + соучредитель), а деньги получались с помощью android market'а, скрипта на bash, который генерировал живые обои на основе самописной рыбы приложения, ffmpeg и видео, купленных за ценник в районе 100$ за 1000 клипов со стока.
    На рынке андроид деньги таки есть, хоть и не такие большие, как в appStore, но надо же понимать, что узкоспециализированная ниша = низкому спросу. У Вас прямо ошибка выжившего наоборот.
  • Если ли пример не убиваемого сервиса который будет работать постоянно, в фоне, пока не выключишь на всех версиях андроид?

    akaish
    @akaish
    alterEgoChaos, публичное API это и есть забор, за которым что-то лежит. У нормального программиста на заборе написано дрова и за забором - дрова. У плохого программиста на заборе написано дрова, а за забором - кирпичи. А есть "волшебные" программисты, они закрашивают надписи на заборах нормальных программистов и пишут слова из трех букв. Программирование стоит на стандартах и соглашениях, принятых в коммьюнити и апеллировать к тому, что программа, написанная с учетом общепринятых соглашений, не будет работать в некоторых случаях по причине деятельности "волшебных" программистов не имеет смысла. Это проблемы волшебников.

    А теперь про AlarmManager. Если вкратце, Вы некомпетентны в данном вопросе. AlarmManager жрет батарею не из-за того, что реализация системного таймера почему-то требует много ресурсов CPU, а потому что связный код пробуждает устройство и выполняет какие-либо операции (чаще всего сетевые). И это ничем не отличается от PARTIAL_LOCK для сервиса GPS. Так как для получение данных от GPS в реальном времени устройство итак не может находиться в состоянии полного простоя. И фоновый сервис будет также жрать батарею, потому что будет препятствовать сну устройства, ахаха. Читаем забор: Scheduling Alarms, вникаем во флаги, inexact repeating, отличия RTC от ELAPSED_REALTIME, WAKEUP версии этих флагов. Как говорится, слышал звон, а не знает, где он.
    AlarmManager - лучший из вариантов для обеспечения совместимости с версиями Android 4.x, но можно посмотреть и в сторону нового API. Вот простая статейка, объясняющая доходчивым языком про запуск по расписанию на свежих API: Choosing the Right Background Scheduler in Android
  • Если ли пример не убиваемого сервиса который будет работать постоянно, в фоне, пока не выключишь на всех версиях андроид?

    akaish
    @akaish
    alterEgoChaos, если какой-нибудь Xiaomi наворачивает задокументированное поведение Android, то это проблемы пользователей Xiaomi. Это не магия, это публичный API андроида. Если Вам это кажется магией, то, увы, Вы просто некомпетентны в Android. И если Ваше утверждение по поводу Xiaomi верно - то разработчики Xiaomi просто мудаки.