📜 ⬆️ ⬇️

Android Cuvettes, Part 2: SDK and Libraries

When developing for Android, you always need to be alert. Step to the left / step to the right - and now another hour has passed after the debug. The cuvettes can be anything from normal bugs in the SDK and ending with non-obvious method names with context-sensitive results (yes, Fragment.getFragmentManager () , I mean you).

In the previous article , we described cuvettes “on the surface” of the SDK, into which it is very easy to please. This time the cuvettes will be deeper, wiser and more specific. There will also be a few points related to Retrofit 2 & Gson .
image


1. GridLayout does not respond to layout_weight


Situation

Sometimes it happens that the usual way to create an object with a heap does not fit:
Ordinary form
image

Such a situation could be, for example, landscape form mapping. I would like in this case to have something like this:
Form for landscape
image

How to make the same alignment 50 to 50? There are several basic approaches :

However, they all have their drawbacks:

Moreover, it is often necessary to use the magic number 0dp & weight = 1 to achieve a flexible design. Neither TableLayout nor RelativeLayout will help you here. When you first try to use something like TextView.setEllipsize () , problems and pain will begin.
And then you probably noticed that I missed another element. It would seem that GridLayout comes to the rescue, but it also turns out to be useless due to the fact that it does not support the layout_weight property. So what to do?
')
Decision

Until now, there was really nothing to do - either suffer with RelativeLayout , or use LinearLayout , or fill in everything programmatically (for especially perverted ones).
However, since version 21, GridLayout has finally begun to support the layout_weight property and, most importantly, this change was added to AppCompat as android.support.v7.widget.GridLayout !
By the time I found out about this (and generally that the usual GridLayout wanted to weigh on my weight ), I spent at least a week trying to understand why my layout swam to the right (like this ). Perhaps this is one of the most important innovations, which, for some reason, remained without proper attention. Fortunately, stackoverflow ( 1 , 2 ) responses are already beginning to be added.
I also advise you to look at the page to the new PercentRelativeLayout and PercentFrameLayout - this is really a bomb. The name speaks for itself and allows you to make a highly responsive design. iOS'niki appreciate. And oh yeah, it is in AppCompat .

2. Are Fragment.isRemoving () and Acitivity.isFinishing () equal?


Situation

Once I wanted to write my PresenterManager as a singleton (hello from MVP ). In order to remove Presenters in time, I used Activity.isFinishing () , collecting id Presenter 's of activites and deleting them with it. Naturally, this method worked poorly in the case of NavigationView - fragments changed through FragmentTransaction.replace () , Presenter 's were accumulated and everything went under the tail of the cat.
Googling smaltsa , was found method Fragment.isRemoving () , which seems to do the same thing, but for fragments. I rewrote PresenterManager's code and was pleased. The end…

Decision

... came my quiet life when I tried to make it work. Honestly, I tried this way and that, but the behavior of this method is fundamentally different from Activity.isFinishing () . Google was wrong. If you ever have a similar problem, think three times before using Fragment.isRemoving () . I'm serious . Especially pay attention to the logs when you rotate the screen.

By the way, with Acitivity.isFinishing (), too, everything is not so smooth: minimize the application with> 1 activation in the stack, wait for a situation of low memory, go back and use Up Navigation and * voila *! .. This was a simple recipe for fucking an Activity. isFinishing () == false to activate, which you will never see again.
UPD : As the bejibx user correctly noted, the remark about Activity.isFinishing () is not quite right. To understand why, it is better to read the comment thread .

3. Header / Footer in RecyclerView


Situation

A common task when implementing pagination is to display the ProgressBar while loading new data.
Unlike ListView , RecyclerView has much more potential - which is what RecyclerView.Adapter.notifyItemRangeInserted () is worth in comparison with the ListView headache .
However, having tried to use it in the project instead of the ListView , you immediately come across a lot of nuances: where is the ListView.setDivider () property? Where is something like ListView.addHeaderView () ? What else for RecyclerView.Adapter.getItemViewType () , etc., etc.
It is easy to understand that with all this dump of information, but something unpleasant still remains. Adding a Divider / Header makes writing code clouds. What to say about complex layout 'ah? It was possible to do RecheclerView with 4 different Header 's and Footer 's with controllers. Let's just say, sad and dejected, I walked for a very long time.

Decision

In fact, everything is not so bad if you know what to look for. The main problem RecyclerView (and its main advantage) is that you can do anything with it. There is practically no framework. Hence the problem: if you want Header , do it yourself. But fortunately, do it yourself, others have already done for us, so let's take advantage.
Typical problems and their solutions:

Although for me it still remains a mystery - why it was impossible to make some SimpleDivider / SimpleHeaderAdapter , etc. Immediately in the SDK?

4. Acceleration with RecyclerView.Adapter.setHasStableIds ()


What's wrong with him?

So much problem, how much lack of documentation . This is what it says:
If you want to see the data set. If the item is not located the same.

And here people are divided into two types. First: everything is clear. Second: Cho does that even mean that?
The problem is that even if you attributed yourself to the first people, you may be puzzled by the question: why this method? Yes, yes, to return a unique ID ! I know. But why is it necessary? And no, the answer is “Google writes that it will be scrolling faster!” Will not suit me.

But what's the matter

Acceleration from RecyclerView.Adapter.setHasStableIds () is really possible to get, but only in one case - if you everywhere use RecyclerView.Adapter.notifyDataSetChanged () (and here they deign to write why you need a stable id) . If you have static data, then this method will not give you anything, and maybe even slow down a bit due to internal ID checks. I found out about it only after reading the source code, and a bit later I stumbled upon this article .

5. WebView


Situation

The task is to get html text from the server and display it on the screen. The text is sent by the server as "& lt; html & gt;" . Everything. This is the whole task. Complicated? There is a WebView that can display html in a couple of lines. Why, even TextView can do it! Once or twice and ready ... yes? .. no? .. well, should it be ?!

Decision

Unfortunately, everything is not so smooth:


6. Gson : EnumSet bitmask


When / Where / Why?

(The situation is specific and does not apply directly to Android’s problems, but decided to add it as a seed before the next cuvette)
In response to one of the api- requests, a bit mask of access rights comes in the form of int 'a. It is necessary to process the elements of this mask.
The first thing that comes to mind is the int 'constants and bit operations for checks. Sure, it always works. But what if you want more? What about EnumSet ?
“No problem” - the bearded progger will answer and break the architecture of the models into several more levels: POJO, Model, Entity, UiModel and what else the devil is not joking. But if you want laziness and no extra. classes? What then?

Decision

We create the enum we need by taking care of the “bitness” of the names in @SerializedName :
enum access
public enum Access { @SerializedName("1") CREATE, @SerializedName("2") READ; @SerializedName("4") UPDATE; @SerializedName("8") DELETE; } 


Define a JsonDeserializer to deserialize from json to EnumSet :
EnumMaskConverter
 public class EnumMaskConverter<E extends Enum<E>> implements JsonDeserializer<EnumSet<E>> { Class<E> enumClass; public EnumMaskConverter(Class<E> enumClass) { this.enumClass = enumClass; } @Override public EnumSet<E> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { long mask = json.getAsLong(); EnumSet<E> set = EnumSet.noneOf(enumClass); for (E bit : enumClass.getEnumConstants()) { final String value = EnumUtils.GetSerializedNameValue(bit); assert value != null; long key = Integer.valueOf(value); if ((mask & key) != 0) { set.add(bit); } } return set; } } 


And add it to Gson :
Gsonbuilder
 GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter((new TypeToken<EnumSet<Access>>() {}).getType(), new EnumMaskConverter<>(Access.class)); Gson gson = gsonBuilder.create(); 


As a result:
Using
 class MyModel { @SerializedName("mask") public EnumSet<Access> access; } /* ...some lines later... */ if (myModel.access.containsAll(EnumSet.of(Access.READ, Access.UPDATE, Access.DELETE))) { /* do something really cool */ } 



7. Retrofit : Enum in @GET request


Situation

Let's start with the settings. Gson is formed as before. Retrofit is created like this:
new Retrofit.Builder ()
 retrofit = new Retrofit.Builder() .baseUrl(ApiConstants.API_ENDPOINT) .client(httpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); 


And the data looks like this:
enum season
 public enum Season { @SerializedName("3") AUTUMN, @SerializedName("1") SPRING; } 


Thanks to the ability of Gson to directly parse the enum through the usual @SerializedName , it became possible to get rid of the need to create any additional classes-layers. All data will immediately go from requests to the Model . Everything is fine:
Retrofit Service
 public interface MonthApi { @GET("index.php?page[api]=selectors") Observable<MonthSelector> getPriorityMonthSelector(); @GET("index.php?page[api]=years") Observable<Month> getFirstMonth(@Query("season") Season season); } 


Application
 class MonthSelector { @SerializedName("season") public Season season; } /* ...some mouses later... */ MonthSelector selector = monthApi.getPriorityMonthSelector(); Season season = selector.season; /* ...some cats later... */ Month month = monthApi.getFirstMonth(season); 


And now, dear experts, attention to the question! What went wrong and why is it not working?

Decision

I specifically omitted the information that it is not working here. The fact is that if you look in the logs, then the monthApi.getFirstMonth (season) query will be processed as index.php? Page [api] = years & season_lookup = AUTUMN ... "uh, what's the matter?" I will say. What is your answer? Why such a result? Haven't guessed yet? Then you are trapped.
When I ran into this task, it took me several hours of searching the source code to understand one thing (or rather even remember): yes, Gson is not used when sending @GET / @POST and other similar _queries_ in general! Indeed, when was the last time you saw something like index.php? Page [api] = years & season_lookup = {a: 123; b: 321} ? This makes no sense. Retrofit 2 uses Gson only when converting Body , but not for the requests themselves. Eventually? just used season.toString () - hence the result.
However, if you really want to sooo (and I am one of them) use enum with Gson conversion in the request, then here you are another converter, everything is as usual.

8. Retrofit : transfer auth-token


And finally, I would like to say one thing to those who write like this:
Any Retrofit Service
 public interface CoolApi { @GET("index.php?page[api]=need") Observable<Data> just(@Header("auth-token") String authToken); // ^ auth-token @GET("index.php?page[api]=more") Observable<Data> not(@Header("auth-token") String authToken); // ^ auth-token   @GET("index.php?page[api]=gold") Observable<Data> doIt(@Header("auth-token") String authToken); // ^ auth-token  101 ! } 


Begin to use the Interceptor 'ora already! I understand that it is very simple to use Retrofit and therefore no one reads the documentation, but when you sit for 3 hours and clean the code not only from the auth-token , but also from any specific current_location, battery_level, busy_status - great sadness overtakes (do not ask why battery_level in each request. Himself in shock). You can read about it here .

Instead of conclusion


Well, this time there was a lot more text than I had planned. Some less interesting ditches had to be thrown out, while others I decided to leave for the next time.
Contrary to the promise of the previous part , this time I tried to make you not “google first”, but first of all think “why am I doing this?”. Sometimes the problem is not created by the SDK or the library, but by the programmer himself, and, unfortunately, in this case, everything is far more pitiable. Do not underestimate the chosen tool, as well as not overestimate it.
In general, if you like android and / or you plan to do it - always keep yourself up to date with world trends . Well, or look for a news resource more convenient for you. There you can find a lot of information about the Android SDK , popular libraries , etc., etc.

Source: https://habr.com/ru/post/280190/


All Articles