⬆️ ⬇️

Tips for creating applications by the end of recruitment at the Yandex Mobile Development School

The recruitment to the School of Mobile Development, which will traditionally be held in Moscow, will be completed very soon. It will focus on practical exercises - team mini-hackathons, which, in addition to writing code, will need to make decisions, deal with the controversial issues that have arisen and deal with long-term planning. To help students - each team individually - will be the guys from Yandex. More details about the upcoming school can be found here . We will finish accepting applications on May 6 at 11:59 pm Moscow time, and while there is still time to complete the tasks, we decided to make out last year’s version. You will learn what mistakes novice developers often make and what you should pay attention to when writing the code for your first application.







Traditionally, the task is built so that we can pay attention to different aspects of development. These include application architecture, stability, performance, layout, usability. All components are equally important: even ideally combed and layered code with a high probability will not pass the selection, if there are problems in the interface or crashes during the execution of basic user scripts. There is no universal recipe for making an ideal application that is guaranteed to be selected. There are many approaches to the development and different options for building architecture, but one of the components of success is positive user experience. The product should create the impression of completeness, no matter how much useful functionality it has, screens or elements.



Content





Repository and coding style



The theater begins with a hanger, and the project begins with a repository.

')

Before you start developing, add the appropriate .gitignore file so that the extra files don’t get into your repository for sure. So, it is not recommended to store in the repository your IDE's configuration file, files with keys for signing the application, and any usernames and passwords.



During development, follow the golden middle - you should not commit a half-project at a time, but many small commits in one or two lines should be avoided. Write clear messages to each commit. For individual features, you should use branches to develop features independently and always have the current (and working) version of the application in the master branch. Try first to implement the minimum necessary features, or only then all the "beautiful" and improvements. If you need to switch to another branch and you don’t want to commit an incomplete code (but you don’t want to lose it either), it’s convenient to use stashes .



The code is written once, and is read many times, so it is important that the coding style and the name of the variables are adequate. Leave meaningful comments if you need them, do not add magic numbers and do not mix code from different libraries that do the same thing. If the code is written by one developer, then it’s easy to maintain a common style and approach, and in a large team you can use different analyzers or create your own style and commit it to the version control system so that it can be picked up from each developer.



Here's what else you should pay attention:





SDK and libraries



Using the Android SDK and libraries often raises questions from novice developers. Below we list some typical problems and their solutions.



Incorrect work with lists

To use LinearLayout + ScrollView to display multiple view of the same type means to make a mistake. This method creates a load on the memory, all the views are in it at the same time. The scroll starts to slow down. In this case, it is correct to use at least a ListView, or better, a more advanced RecyclerView . These containers provide re-use of the view (using the adapter mechanism). The views that are not currently visible to the user are filled with fresh data.



But here you can make a mistake. RecyclerView, unlike ListView, forces the developer to use the ViewHolder pattern. As its name implies, this pattern consists in creating a class whose objects store references to the views already found in the hierarchy. You should not call findViewById every time you need to put some value. You need to do this once, during the creation of the holder.



State saving

Another big problem for beginners is maintaining the state of the application when the configuration changes (for example, when the orientation, language, screen size , etc. ) changes. It often happens that beginners do not test such cases. This leads to user dissatisfaction or even crash. Sometimes developers stop at fixing the orientation - which, of course, does not save. There are several ways to support out-of-box configuration changes. We will not go into it, you can always read the documentation . The main thing to remember is that such a problem exists, to test, to pay attention to the dialogues (it is better to use DialogFragment , and not AlertDialog), since they must also restore the state. It must be remembered that storing the state of the screen in a persistent repository (for example, SharedPreferences) is not recommended. As a result, a “clean” launch can lead to an already existing state, and this is not particularly idiomatic.



Data loading and caching

It's no secret that walking on the network on the UI stream in Android is prohibited . There are many libraries for the network, for example OkHttp , if you have REST add Retrofit , and for downloading pictures Glide or Picasso. Thus, it is no longer necessary to use HttpUrlConnection inside AsyncTask for a long time (especially for uploading images - which is actually not easy). But many beginners do not think that, for example, reading and writing to the database is also an I / O operation that can affect the UX due to the freeze of the main thread. The same applies to reading files, accessing ContentProviders. All such operations should occur in a separate thread. What to use for this - everyone decides for himself; it is not possible to describe all the variety of solutions in this format.



Change the system behavior of the Back button

Often there is a temptation to hang there a reaction that is not standard for the system, including completely swallowing this event (process, but do nothing at the same time). So - better not. The user of this OS has habits, and Back should lead to the previous screen or lead to exit from the application.



Architecture



The architecture of an Android application is often a sore spot even for experienced developers. In this section we will give some general tips. Whether to follow them is up to you.



Architecture and decomposition

The lack of decomposition is a sign of bad architecture. The platform does not give clear instructions on how to write applications correctly, so there is a big temptation to write all the code in Activity or Fragment. As a result, the code becomes untestable, it is difficult to make any changes. To avoid this situation, you can use modern architectural practices and design patterns. Read about MVP, MVVM, MVI. Write the first three classes and cover them with tests. You will probably notice that writing tests is difficult and you need to think about architecture.



Brute force with abstractions

The repositories on GitHub often show the “canonical” implementation of a particular architecture. Blindly copying other architectural solutions may not only be of no use, but also complicate your work and degrade the performance of the application. Try not to write meaningless code just because it is “more correct” or “cleaner”, and in each case correlate the benefits of the solution and the complexity of its support and understanding.



Implicit connections between components, impure functions

Quite often, the value of some global variable is statically stored in Application and changed from different parts of the application. Since the execution of seemingly unconnected parts of the code is influenced by a global variable, in the application there can be states that the developer did not expect to receive during execution. The worst thing is that such bugs are very difficult to catch and reproduce. Try to write "clean" methods and explicitly specify dependencies in class constructors or in method arguments.



UI (resources, graphics, layout)



The appearance and usability of the application are very important. Convenient design is difficult to make, but we advise you to pay attention at least to the points outlined below.



Quality layout



The performance of Android is quite dependent on the quality of the layout. Complex container hierarchies are longer calculated. In addition, when nesting is possible, crashes during rendering and drawing (due to stack overflow) are possible. With the advent of ConstraintLayout (and especially with the installation of its root xml element when creating from a wizard), newcomers have become significantly more complicated hierarchy. Nested Relative / ConstraintLayout are most commonly used, which is fundamentally wrong. ConstraintLayout is designed to make the hierarchy flat. We recommend reading at least the introduction to the documentation and try to use this class correctly. Also avoid unnecessary nesting of the ViewGroup.



Design



Not all developers have designer skills. Often there is an inconsistent color palette in the application that the developer likes, but most users do not. It happens that the inscription can not read, not only people with disabilities (eg, color blind), but other users. Standard check: if the inscriptions are visible in grayscale, most likely, most users will also see them. For a choice of color palette and general design principles, you can see two links: material.io and www.materialpalette.com .



What else is worth doing





Error processing



An army of users of your application, like a good tester, will always find a way to break something for you. Be sure to check the application in unusual situations. It is important to develop the tester's mindset and to foresee all kinds of non-standard usage scenarios (starting without the Internet, adding a record to the history two times, a sudden change of device settings, etc.). After finishing work on any functionality, it is important to test this functionality and run a few scripts to make sure that everything works. The problem is that testing your code is difficult, as there is an inner feeling and hope that everything works correctly. Nobody wants to write non-working code. Do not go on about their feelings. It is better to check several scenarios with normal and borderline conditions.



When processing errors, you can go into two extremes - to process everything and everyone without thinking or not to process anything. The second option, obviously, pops up pretty quickly, but the first one is more cunning and leads to unpredictable consequences.



Let us dwell in some detail on some examples of such errors.



try..catch ... everywhere



Such processing, as the name implies, consists in concluding any suspicious (from the point of view of the author) code in a try..catch block. The distribution includes NPE and IndexOutOfBoundsException , IllegalArgumentException, and even OutOfMemoryError , that is, exceptions that usually indicate logical errors in the application, application states from which it cannot be adequately restored. Of course, the correct solution would be the correction of logical errors. In addition, when writing Java code, you can use static analysis and, at a minimum, annotate @NonNull and @Nullable wherever you need. This will help catch NPE.



WeakReference <Everything>



Often, newcomers, upon learning about memory leaks, begin to fear them as fire. With a lack of experience, this may result in wrapping any objects in the WeakReference . Typically, this technique suggests a poor understanding of the life cycle of objects and the connections between them. Here is an example:



public class MyAdapter extends RecyclerView.Adapter<ViewHolder> { private final WeakReference<Context> contextRef; public MyAdapter (@NonNull Context context) { this.contextRef = new WeakReference < > (context); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final Context context = contextRef.get(); return context == null ? null : LayoutInflater.from(context).inflate(R.layout.item, parent, false); } ... } 


Here a reference to Context comes to the adapter's constructor. But the adapter is part of the UI, which is shown in some Activity. Therefore, it should not live longer than Context, and it follows from this that the WeakReference is not needed here.



Another example of using WeakReference:



 public class MyAsyncTask extends AsyncTask<String, String, String> { private final WeakReference<Context> contextRef; public MyAsyncTask(@NonNull Context context) { this.contextRef = new WeakReference<>(context); } ... @Override protected void onPostExecute(@Nullable String result) { final Context context = contextRef.get(); if (context != null && result != null) { Toast.makeText(context, result, Toast.LENGTH_SHORT).show(); } } } 


Nothing has changed in appearance, but AsyncTask is able to survive the Activity in which it was created. Therefore, the use of the WeakReference is justified here.



To avoid memory leaks, first of all, you should understand what is the life cycle of the objects you use. To search for memory leaks in an existing large project, you can use Memory Profiler and LeakCanary .



No cancel requests



Sometimes the cause of errors lies in the absence of cancellation of asynchronous operations, network requests or timers. Consider the timeliness and frequency of these operations, pay attention to the twin API methods like subscribe / unsubscribe, create / destroy, start / cancel.



Tests



Lack of unit tests



We love unit tests for the fact that when creating a product, they help to write better and more supported code, and also serve as a kind of documentation that does not become obsolete with time. Some developers neglect unit tests - they say that they are difficult and long to write, their launch takes a lot of time.



Each of these points can be challenged. Let's start with the paragraph about time costs. Perhaps at the very beginning, the writing of unit tests will take a lot of time, but with experience the speed will increase. In addition, unit tests - an investment in the future, which in the future will allow you to write code faster. The problem with the startup speed is solved even simpler: it is enough to configure Continuous Integration once and run the tests automatically. If unit tests are difficult to write, it means that you are not writing the best quality code. Good architecture makes it easy.



Useless tests



Useless tests not only make it difficult to understand the source code, but also affect the build time of the application, so it is even worse than useless code. There are several cases where tests are useless:



Testing someone else's code

It is assumed that someone else's code already comes with tests, so this testing only takes time.



Tests depend on each other or on external factors.

Test-dependent tests are difficult to maintain, as changing one test affects many others. In addition, testing becomes unpredictable, because tests can be performed in a different order.



Testing implementation details

Tests should take the tested classes as a black box, otherwise when changing the implementation, you will have to change the test itself. This is inconvenient and often ends with deleting or ignoring the test.



Conclusion



Last year we analyzed several hundreds of test tasks and once again made sure that the main causes of problems in the application are quite simple:





To all those who enter this school and those who are developing their first application, we recommend:





useful links



For a novice developer who hasn’t written a single line for Android, it’s helpful to start with Google courses on Udacity (in English with Russian subtitles). If you don’t leave the course after the first few videos and go through to the end, you will get not only good theoretical knowledge, but also the first application that you made yourself!



The Android Developers YouTube channel is just a pile of short tutorial videos and news from the world of Android development. We recommend to see Android Perfomance Patterns , because performance is important, isn't it?



To keep up with trends and always know where to go from here, you can learn a little bit each week using the Android Weekly newsletter (in English) or listen to podcasts on your way home, such as Fragmented and AndroidDevPodcast .



Getting to know Kotlin is best to start with Kotlin koans - you can get an idea of ​​the basic syntax of the language. A video course from the Computer Science Center is worth going through to learn more about the language, learn about its pros and cons, and understand why it is implemented that way. In the case of Java, there is also a fundamental course from the Computer Science Center - theoretical knowledge is supported by a large number of practical tasks.



You can view implementations of common architectures in the Android Architecture Blueprints repository.



To study the architectural patterns are often recommended books that are difficult for a beginner to master. SourceMaking is ideally suited for the first acquaintance - the material is well structured, and there are clear metaphors and examples of implementation in Java to most of the patterns.



Having completed the online course from Computer Science Center on algorithms , you will get acquainted with the basic algorithmic methods and stuff your hand by implementing the classical algorithms. Also in the course you can find a good bibliography. If practice is not enough, you can always practice at www.hackerrank.com . Just do not spend time on tasks with the level of complexity Easy - they are too simple.



Learning the basics of git will allow learngitbranching.js.org , and more advanced features can be mastered in the Enki mobile app.



Those who decide to use git exclusively from the console, it is better to learn the basic commands of Vim, for example using the game vim-adventures.com .

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



All Articles