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 .

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:
Such a situation could be, for example,
landscape form mapping. I would like in this case to have something like this:
How to make the same alignment 50 to 50? There are several
basic approaches :
However, they all have their drawbacks:
- The abundance of LinearLayout leads to xml's monstrosity
, and it leads to the death of seals . - RelativeLayout complicates the change in the future (swap a few lines in the form or add a separator will be the one more task. I’m completely silent about View.setVisibility (View.GONE ).
- Well, nobody uses TableLayout at all ... or uses it, but rarely. I do not know such people.
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 .
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 .
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:
- Headings for groups of elements (for example, in the dictionary “A” will be the heading for all words beginning with this letter) - the easiest way to do this is through a single item-layout , without adding the 2nd unnecessary type ViewHolder 'a. Add a check that the current element marks the transition from one letter to another and turn on the header hidden in the layout via View.VISIBLE .
- Simple divider - copy-paste this code into the project. No extra fraud. Works through RecyclerView.addItemDecoration ()
- Add Header / Footer / Drag & Drop etc. - if you do handles, then either get a new type for each new ViewHolder (I do not advise), or do a WrapperAdapter (much nicer). But it is even better to look here and choose your favorite lib. Personally, I like two at once: FastAdapter and UltimateRecyclerView
- Need pagination , but too lazy to mess with Header / Footer for ProgressBar 's - Paginate library from one of the developers of twitter.
Although for me it still remains a mystery - why it was impossible to make some
SimpleDivider / SimpleHeaderAdapter , etc. Immediately in the SDK?
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 .
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:
- Let's start with the fact that there is no method like HtmlUtils.unescape () in the Android SDK. If you want a "& lt;" turn it into "<" , then the easiest way (besides prescribing regex 'and handles) is to connect apache with its StringUtils.unescapeHtml4 () .
- The next problem will be artifacts when scrolling. All of a sudden (yes, the Android SDK?), The webview will flash in black. What to do - is told here and here . Personally, I was helped only by a combination of these approaches.
- And if you have not yet been surprised by the abundance of problems from such a simple task, then here is the point: you need to display the ProgressBar before the html page has been rendered. And then everything is bad. That is really bad. All solutions presented on stackoverflow work through time or do not work at all ( tyk , tyk ). The only hitherto working method was using WebView.setPictureListener () , however, it is now declared deprecated and there is nothing to be done about it.
As a result, the only thing that can be advised is to refuse ProgressBar 'a. Or, if it’s really, really, really impatient, add it directly to the html code, checking the readiness of the page via javascript . But this is for the club elite mazahistov.
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 accesspublic 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; } if (myModel.access.containsAll(EnumSet.of(Access.READ, Access.UPDATE, Access.DELETE))) { }
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; } MonthSelector selector = monthApi.getPriorityMonthSelector(); Season season = selector.season; 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);
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.