• Как получать push-уведомления, когда приложение закрыто?

    enq3
    @enq3
    Android engineer at #ITX5
    Никак.

    RTFM https://firebase.google.com/docs/cloud-messaging/a...

    When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.

    This includes messages that contain both notification and data payload (and all messages sent from the Notifications console). In these cases, the notification is delivered to the device's system tray, and the data payload is delivered in the extras of the intent of your launcher Activity.
    Ответ написан
  • Управление векторной инфографикой в приложении Android с целью визуализации возможно?

    enq3
    @enq3
    Android engineer at #ITX5
    Готовые анимации из Adobe After Effects использовать через https://airbnb.design/lottie/
    Свои анимации на основе svg делать тут https://shapeshifter.design/
    У шифтера в File-Demo есть примеры.
    Ответ написан
    Комментировать
  • Почему приложение лагает после создания нескольких фрагментов?

    enq3
    @enq3
    Android engineer at #ITX5
    Евгений Низамиев: Тормозить конечно будет при добавлении такого количества фрагментов в одну активность.
    Большие списки с повторяющимися элементами лучше делать через адаптеры.
    В данном случае лучше взять ViewPager и FragmentPagerAdapter (FragmentStatePagerAdapter).
    Ответ написан
    Комментировать
  • Retrofit, нужно ли использовать expire/max-age?

    enq3
    @enq3
    Android engineer at #ITX5
    Если посмотреть через веб-дебагер в том же хроме на заголовки запроса и ответа, то сервер возвращает куки в Set-Cookie. А в запросе уходит через Cookie.
    Следовательно:
    Call<Profile> getCurrentUser(@Header("Cookie") String cookieA);

    Dante Faustoff прав, срок жизни куки не нужен.
    Ответ написан
    Комментировать
  • Как добавить Fragment в LinearLayout?

    enq3
    @enq3
    Android engineer at #ITX5
    Самое банальное вот так:
    @Override
            protected void onPostExecute(Spanned spanned) {
                super.onPostExecute(spanned);
                title.setText(strTitle);
                textView.setText(spanned);
                for (List<String> id : ids) {
                    VideoFragment f = VideoFragment.newInstance(id);
                    FrameLayout frameLayout = new FrameLayout(NewsContent.this);
                    frameLayout.setTag(id);
                    linearLayout.addView(frameLayout);
                    getSupportFragmentManager().beginTransaction().replace(frameLayout.getId(), f).commit();
                }
                progressDialog.dismiss();
    
            }
        }


    activity_main.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <LinearLayout
                android:id="@+id/scrollContainer"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"/>
        </ScrollView>
    </RelativeLayout>


    MainActivity.java
    public class MainActivity extends AppCompatActivity {
        LinearLayout linearLayout;
        String[] ids = new String[]{"1", "2", "3", "4", "5"};
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            linearLayout = (LinearLayout) findViewById(R.id.scrollContainer);
            int position = 0;
            for (String id : ids) {
                FrameLayout fm = new FrameLayout(this);
                fm.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500));
                linearLayout.addView(fm);
                fm.setId(++position);
                BlankFragment fragment = BlankFragment.newInstance(id);
                getFragmentManager().beginTransaction().replace(position, fragment, id).commit();
            }
        }
    }


    fragment_blank.xml
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/hello_blank_fragment" />
    </FrameLayout>


    BlankFragment.java
    public class BlankFragment extends Fragment {
        private static final String ARG_PARAM1 = "param1";
        private String mParam1;
        private TextView title;
    
        public static BlankFragment newInstance(String param1) {
            BlankFragment fragment = new BlankFragment();
            Bundle args = new Bundle();
            args.putString(ARG_PARAM1, param1);
            fragment.setArguments(args);
            return fragment;
        }
    
        public BlankFragment() {}
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mParam1 = getArguments().getString(ARG_PARAM1);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.fragment_blank, container, false);
            title = (TextView) v.findViewById(R.id.title);
            title.setText(mParam1);
            return v;
        }
    }
    Ответ написан
  • Retrofit как авторизоваться?

    enq3
    @enq3
    Android engineer at #ITX5
    Документация нам говорит так:
    A request Header can be updated dynamically using the @Header annotation. A corresponding parameter must be provided to the @Header. If the value is null, the header will be omitted. Otherwise, toString will be called on the value, and the result used.
    @GET("/user")
    Call<User> getUser(@Header("Authorization") String authorization)

    Вообщем берем случай, когда апи нет как такового, и надо залогиниться на сайт отправив запрос, как будто мы веб-клиент.
    Подопытный kotomatrix.ru.

    При парсинге запросов выяснилось, что авторизация происходит через отправку формы.
    KotomatrixService.java
    public interface KotomatrixService {
        @FormUrlEncoded
        @POST("http://kotomatrix.ru")
        Call<String> login(@Field("login") String login, @Field("password") String pass, @Field("act") String act, @Field("remember") String remember);
    }


    ApiFactory.java
    public class ApiFactory {
        private static final int CONNECT_TIMEOUT = 15;
        private static final int WRITE_TIMEOUT = 60;
        private static final int TIMEOUT = 60;
        private static final OkHttpClient CLIENT = new OkHttpClient();
    
        static {
            CLIENT.setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
            CLIENT.setWriteTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
            CLIENT.setReadTimeout(TIMEOUT, TimeUnit.SECONDS);
        }
    
        @NonNull
        public static KotomatrixService getKotomatrixService() {
            return getRetrofit().create(KotomatrixService.class);
        }
    
        @NonNull
        private static Retrofit getRetrofit() {
            return new Retrofit.Builder()
                    .baseUrl("http://kotomatrix.ru")
                    .addConverter(String.class, new StringConverter())
                    .client(CLIENT)
                    .build();
        }
    }


    StringConverter.java
    public final class StringConverter implements Converter<String> {
        @Override
        public String fromBody(ResponseBody body) throws IOException {
            return body.string();
        }
    
        @Override
        public RequestBody toBody(String value) {
            return RequestBody.create(MediaType.parse("text/plain"), value);
        }
    }


    MainActivity.java
    public class MainActivity extends AppCompatActivity implements Callback<String> {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            KotomatrixService service = ApiFactory.getKotomatrixService();
            Call<String> call = service.login("testUser", "testUser", "login", "true");
            call.enqueue(MainActivity.this);
        }
    
        @Override
        public void onResponse(Response<String> response) {
            if (response.isSuccess()) {
                // ищем куку и сохраняем
            }
        }
    
        @Override
        public void onFailure(Throwable t) {
        }
    }


    В данном примере при успешной авторизации сервер отдаст kotomatrixCOOK, ищем значение тут response.rawResponse.headers и сохраняем в SharedPreferences.
    Ответ написан
    8 комментариев
  • ListView не воспринимает string-array в качестве entries, как исправить?

    enq3
    @enq3
    Android engineer at #ITX5
    Проведем исследование.

    ListView.java
    public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
    
            final TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
    
            final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
            if (entries != null) {
                setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
            }
    ...


    Наследуемся:
    attrs.xml
    <resources>
        <declare-styleable name="ListView">
            <attr name="entries" format="reference"/>
        </declare-styleable>
    </resources>

    MyListView.java
    public class MyListView extends ListView {
        public MyListView(Context context) {
            super(context);
        }
    
        public MyListView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListView, 0, 0);
            final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
            if (entries != null) {
                setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, entries));
            }
            a.recycle();
        }
    }

    MyListView.java
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <test.MyListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:entries="@array/options">
        </test.MyListView>
    </RelativeLayout>


    Работает как-то не очень. У меня отобразился только 1-ый элемент списка.

    Меняем так:
    MyListView.java
    public class MyListView extends ListView {
        public MyListView(Context context) {
            super(context);
        }
    
        public MyListView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListView, 0, 0);
            final int resId = a.getResourceId(R.styleable.ListView_entries, 0);
            final CharSequence[] entries = getResources().getTextArray(resId);
            if (entries != null) {
                setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, entries));
            }
            a.recycle();
        }
    }

    И весь список отобразился.
    Отличие вот в этом:
    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListView, 0, 0);
    final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);


    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListView, 0, 0);
    final int resId = a.getResourceId(R.styleable.ListView_entries, 0);
    final CharSequence[] entries = getResources().getTextArray(resId);

    Может и ошибаюсь, но проблема скорее всего где-то внутри TypedArray.
    Ответ написан
    3 комментария
  • Отказ в обслуживании роутером всех клиентов кроме одного во время нагрузки. В чем может быть дело и как исправить?

    enq3
    @enq3
    Android engineer at #ITX5
    Выключите Task offload в настройках адаптера (свойства подключения -> настроить -> дополнительно).
    Task Offload - перекладывание части работы с процессора на сетевую карту. Под большой нагрузкой как раз может отваливаться сеть.
    Ответ написан
    1 комментарий
  • Как быстро сравнить два списка?

    enq3
    @enq3
    Android engineer at #ITX5
    Если я все правильно понял, то клиент должен периодически запрашивать данные с сервера.
    Предлагаю такой вариант:
    Заведем еще два поля в таблице на сервере: Version и Deleted.
    При каждом изменении записи ее версия = максимальная версия в текущей таблице + 1.
    UID|HASH|Version|Deleted
    1|c4ca4238a0b923820dcc509a6f75849b|1|0
    2|c81e728d9d4c2f636f067f89cc14862c|2|0
    3|eccbc87e4b5ce2fe28308fd9f2a7baf3|4|0
    4|a87ff679a2f3e71d9181a67b7542122c|8|1
    5|e4da3b7fbbce2345d7772b0674a318d5|6|0

    Клиент запрашивает данные старше 3-ей версии.
    Вернется:
    {
        "version": 8,
        "changed": [
            {
                "UID": 3,
                "HASH": "eccbc87e4b5ce2fe28308fd9f2a7baf3"
            },
            {
                "UID": 5,
                "HASH": "e4da3b7fbbce2345d7772b0674a318d5"
            }
        ],
        "deleted": [
            {
                "UID": 4
            }
        ]
    }
    Не надо бегать по спискам и сравнивать хэши.
    Таким образом реализуется гибкий подход к клиентам с разной степенью актуальности данных. Это экономия трафика и экономия ресурсов устройства.
    Ответ написан
  • Как избавиться от наложения фрагментов друг на друга?

    enq3
    @enq3
    Android engineer at #ITX5
    MainActivity.java
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            Fragment fragment = MainActivityFragment.newInstance(this);
            ft.replace(R.id.main, fragment);
            ft.commit();
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                FragmentManager fm = getSupportFragmentManager();
                FragmentTransaction ft = fm.beginTransaction();
                Fragment fragment = SettingsActivityFragment.newInstance(this);
                ft.replace(R.id.main, fragment);
                ft.addToBackStack(null);
                ft.commit();
            }
    
            return super.onOptionsItemSelected(item);
        }
    }


    MainActivityFragment.java
    public class MainActivityFragment extends Fragment {
        public static Fragment newInstance(Context context) {
            MainActivityFragment f = new MainActivityFragment();
            return f;
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_main, container, false);
        }
    }


    SettingsActivityFragment.java
    public class SettingsActivityFragment extends Fragment {
        public static Fragment newInstance(Context context) {
            SettingsActivityFragment f = new SettingsActivityFragment();
            return f;
        }
        
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_settings_activity, container, false);
        }
    }


    activity_main.xml
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />


    fragment_main.xml
    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/AC_ListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#fff"></ListView>


    fragment_settings_activity.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="top">
    
        <ScrollView
            android:id="@+id/scrollView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
    
                <TextView
                    android:id="@+id/GeneralSettingsText"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:layout_marginTop="5dp"
                    android:text="GeneralSettingsText"
                    android:textStyle="bold" />
    
                <View
                    android:id="@+id/AdvCount"
                    android:layout_width="wrap_content"
                    android:layout_height="1dp"
                    android:layout_gravity="center_vertical"
                    android:layout_marginTop="5dp"
                    android:background="#fccccccc" />
    
                <CheckBox
                    android:id="@+id/checkBox1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="checkBox1" />
    
                <TextView
                    android:id="@+id/ShowedText"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:layout_marginTop="5dp"
                    android:text="ShowedText"
                    android:textStyle="bold" />
    
                <EditText
                    android:id="@+id/editText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:ems="10"
                    android:inputType="textMultiLine"
                    android:lines="3" />
    
    
                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:text="textView2"
                    android:textStyle="bold" />
            </LinearLayout>
        </ScrollView>
    
    </RelativeLayout>
    Ответ написан
    Комментировать
  • Как написать цикл с разной временной паузой для каждой команды - Android?

    enq3
    @enq3
    Android engineer at #ITX5
    Как такой вариант? В коде могут быть ошибки - пишу с планшета.
    byte[] mls = new byte[11];
    mls[0] = addCRC(new byte[]{1, 0x5, 11, 5 ,0, 0});    //10 сек
    mls[1] = addCRC(new byte[]{1, 0x5, 11, 1 ,0, 0});    //0 сек
    mls[2] = addCRC(new byte[]{1, 0x5, 0, 5 ,0, 0});      //30 сек
    mls[3] = addCRC(new byte[]{1, 0x5, 1, 5 ,0, 0});      //5 сек
    mls[4] = addCRC(new byte[]{1, 0x5, 2, 5 ,0, 0});      //5 сек
    mls[5] = addCRC(new byte[]{1, 0x5, 3, 5 ,0, 0});      //5 сек
    mls[6] = addCRC(new byte[]{1, 0x5, 4, 5 ,0, 0});      //5 сек
    mls[7] = addCRC(new byte[]{1, 0x5, 5, 5 ,0, 0});      //5 сек
    mls[8] = addCRC(new byte[]{1, 0x5, 6, 5 ,0, 0});      //5 сек
    mls[9] = addCRC(new byte[]{1, 0x5, 7, 5 ,0, 0});      //5 сек
    mls[10] = addCRC(new byte[]{1, 0x5, 8, 5 ,0, 0});      //5 сек
    
    public int[] delays = new int[11]{10,0,30,5,5,5,5,5,5,5,5};
    int shift;
    public void onClickWrite(View v) { 
        int len = mls.length;
        for (int i = 0; i < len;  i++) {
            shift += delays[i];
            new Handler(Looper.getMainLooper()).postDelayed( new Runnable() {
                public void run(){
                    byte b = mls[i];
                    mPhysicaloid.write(b, b.length);
                }
            }, shift);
        }
    }
    Ответ написан
  • Спад производительности в Android приложении, из-за GC?

    enq3
    @enq3
    Android engineer at #ITX5
    Если не важна точность, то можно сделать так:
    float symWidth = paint.measureText("X");
    for (int i = 0; i < list.size(); i++){        \\ size ~ 30k
            String str = list.get(i);
            float strWidth = symWidth * str.length;
    }

    Точность далека от идеала, если шрифт не моноширинный, но зато быстро.
    Ответ написан
    Комментировать
  • Как сменить фрагмент А на фрагмент Б с перерисовкой Б, если он уже создан. А потом Б на А без перерисовки?

    enq3
    @enq3
    Android engineer at #ITX5
    На вскидку: при нажатии на кнопку в фрагменте Б вызывать getActivity().getSupportFragmentManager().popBackStack();
    Я правильно понял, что при нажатии на кнопку в фрагменте Б, на его месте должен появиться фрагмент А, фактически Вам надо вернуться назад?

    activity_main.xml
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent" android:layout_height="match_parent"
        android:id="@+id/fragment">
    </FrameLayout>


    MainActivity.java
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new MainActivityFragment()).commit();
        }


    MainActivityFragment.java
    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
    
            View view = inflater.inflate(R.layout.fragment_main, container, false);
            btn = (Button) view.findViewById(R.id.button2);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new BlankFragment()).addToBackStack(null).commit();
                }
            });
            return view;
        }


    BlankFragment.java
    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_blank, container, false);
            btn = (Button) view.findViewById(R.id.button);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getActivity().getSupportFragmentManager().popBackStack();
                }
            });
            return view;
        }


    Попробуйте.
    Ответ написан
    3 комментария
  • Перенос Android приложения на другой аккаунт в Google Play?

    enq3
    @enq3
    Android engineer at #ITX5
    Подозреваю, что никак. Только заливать заново приложение.
    Ответ написан
    Комментировать
  • Как избежать "Connection timed out" при погасании экрана устройства?

    enq3
    @enq3
    Android engineer at #ITX5
    Устройство уходит в suspend и старается "усыпить" все лишнее, включая сеть.
    В андроиде есть механизм под названием «частичный wakelock».
    Keeping the Device Awake
    WakeLock
    WakefulBroadcastReceiver
    DownloadManager
    Ответ написан
    Комментировать
  • Как найти ошибку в скрипте виртуальной клавиатуры?

    enq3
    @enq3
    Android engineer at #ITX5
    Может стоит попробовать подписаться на изменение элемента, а не на нажатие клавиши?
    $( "input[type='text']" ).change(function() {
      // Check input( $( this ).val() ) for validity here
    });
    Ответ написан
    6 комментариев
  • Как посмотреть поставил ли я like на фото в Instagram?

    enq3
    @enq3
    Android engineer at #ITX5
    Используйте точку доступа Мои лайки.
    Там есть пагинация.
    Кэшируете всю коллекцию лайкнутых фоток в фоновом потоке в БД.
    Когда надо ищете в БД по id интересующей фотки. Если нашлось совпадение, значит лайкал.
    Да, кэшировать долго и придется переодически обновлять, но по-другому похоже никак.
    Ответ написан
  • Отправка POST запроса на сервер в АНДРОИДЕ(штатными методами из коробки)?

    enq3
    @enq3
    Android engineer at #ITX5
    HttpURLConnection
    URL url = new URL("http://backend.com/api.php");
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
       try {
         urlConnection.setDoOutput(true);
         urlConnection.setChunkedStreamingMode(0);
    
         OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
         writeStream(out);
    
         InputStream in = new BufferedInputStream(urlConnection.getInputStream());
         readStream(in);
        finally {
         urlConnection.disconnect();
       }
    Ответ написан
    Комментировать
  • Как сделать локализацию с JSON?

    enq3
    @enq3
    Android engineer at #ITX5
    Может в вашем случае стоит подумать о хранении ресурсов в облаке? Клиент взависимости от локали выкачивает их, парсит в базу и отображает. К тому же это решит проблему актуализации, вместо перепубликации приложения простое обновление данных.
    Ответ написан
    Комментировать