Android
July 30, 2021

Как развивалась работа с кнопкой в Android?

Кнопки мои кнопки

На днях вышел Jetpack Compose, поэтому решил ностальгически пройтись по тем способам работы с View-слоем, какие были популярны у Android-разработчиков ранее, потому что работа с XML на View-слое уходит в прошлое.

0. Способы "из коробки"

0.1 findViewById

findViewById - нативный вариант работы с кнопкой. Итак, у нас есть в XML какая-то кнопка. Чтобы работать с ней из Java или Kotlin кода - нужно в XML задать ей ID:

 ...
 <Button
    android:id="@+id/button"
    android:text="Button"
    ... />
...

При компиляции приложения aapt создаст класс R, который содержит ресурс IDs для всех ресурсов в каталоге res.

Как правило, с кнопкой нужно работать не в одном каком-то методе жизненного цикла, но и обращаться к ней из класса бизнес-логики, то есть Presenter'а или ViewModel'и и нужно, чтобы кнопка была объявлена полем, заодно повесим слушать кликов что примерно будет выглядеть так:

public class MainActivity extends AppCompatActivity {
  
   Button button;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.nd_activity_main);
      button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
            onClickButtons(v)
         }
      });
   }
   
   void onClickButtons(View view) {
      Toast.makeText(
      this, 
      "Здесь обработка клика", 
      Toast.LENGTH_SHORT).show();
   }
}

0.2 onClick в XML

Этот добавился несколько позже и позволяет задать вызов метода из XML-разметки напрямую. И данный способ не работает во фрагментах, только в Activity. Выглядит это так:

 ...
 <Button
    android:id="@+id/button"
    android:text="Button"
    android:onClick="onClickButtons"
    ... />
 ...
public class MainActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }
   
   void onClickButtons(View view) {
      Toast.makeText(
      this, 
      "Здесь обработка клика", 
      Toast.LENGTH_SHORT).show();
   }
}

0.3 OnClickListener

Здесь нужно было просто реализовать интерфейс в Activity или Fragment'е, что на в итоге приводит к такому:

public class MainActivity extends AppCompatActivity 
implements View.OnClickListener {
 
   Button button;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.nd_activity_main);
      button = (Button) findViewById(R.id.button);
      button.setOnClickListener(this);
   }
   
   @Override
   public void onClick(View v) {
      onClickButtons()
   }
   
   void onClickButtons(View view) {
      Toast.makeText(
      this, 
      "Здесь обработка клика", 
      Toast.LENGTH_SHORT).show();
   }
}


2. ButterKnife

Разработчики - люди ленивые. И им изначально казалось, что совершается слишком много телодвижений для начала работы со виджетом. И большую популярность обрела библиотека ButterKnife, которую написал Jake Wharton. Первая версия библиотеки вышла 5 марта 2013 года. Как выглядел тот же самый, но чуть расширенный код относительно того, что выше (пример позволяет обрабатывать клик двух кнопок):

public class MainActivity extends AppCompatActivity {
  
   @BindView(R.id.button)
   Button button;
   
   @BindView(R.id.button2)
   Button button2;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      ButterKnife.bind(this);
   }
   
   @OnClick({R.id.button, R.id.button2})
   void onClickButtons(View view) {
      switch (view.getId()) {
         case R.id.button:
         Toast.makeText(this,
         "Здесь обработка клика button",
         Toast.LENGTH_SHORT).show();
         break;
         
         case R.id.button2:
         Toast.makeText(
         this, 
         "Здесь обработка клика button2",
         Toast.LENGTH_SHORT).show();
         break;
      }
   }
}


3. DataBinding

В 2015 году уже были release-candidate версии этого решения от Google. Точную дату выхода релизной версии не помню, но по-моему активно заговорили о таком решении после Google IO 2017. Нельзя сказать, что оно было очень популярным, так как суть работы происходит через кодогенерацию, что увеличивает время сборки проектов. Это плохо тем, что в сегодняшние дни очень крупные компании стараются модуляризировать свою архитектуру, чтобы при каких-либо изменениях пересобиралась как можно меньшая часть проекта и соответственно уменьшалось время сборки, хотя они даже не используют DataBinding. Сама суть DataBindinga в связывании напрямую xml-кода и ViewModel-класса бизнес-логики, что позволяло даже писать Java-код в XML, то есть у нас напрямую вызывалась функция класса ViewModel'и через лямбда-выражение:

...
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
    <variable
    name="viewModel"
    type="com.example.project.MainViewModel" />
    ...
 <Button
    android:id="@+id/button"
    android:text="Button"
    android:onClick="@{() -> viewModel.onClickButtons()}"
    ... />
...


4. Kotlin Synthetyc

Если верить тому, как мельком я посмотрел доступные GitHub-репозитории, то разработка Kotlin Synthetyc началась примерно примерно в середине 2016 года. Но данный способ работы со View хоть и был немногословным, но быстро стал Deprecated в версии Kotlin 1.4.20, что датирована 5 мая 2021 года. Принцип работы основан на генерации кода плагином, который автоматически подтягивал нужный import.

 
import kotlinx.android.synthetic.main.activity_main.*
 
class MainActivity: AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.nd_activity_main)
      button.setOnClickListener {
         onClickButtons()
      }
   }
   
   fun onClickButtons() {
      Toast.makeText(
      this, 
      "Здесь обработка клика", 
      Toast.LENGTH_SHORT).show();
   }
}


5. ViewBinding

В феврале 2020 года с выходом Android Gradle plugin 3.6.0 - разработчики смогли подключать ViewBinding для работы со View в своих проектах. Данный метод тоже основан на генерации binding-классов, но ликвидирован недостаток DataBindinga в виде очень долгого времени сборки, ну и соответственно нет возможности писать код в XML.

class MainActivity: AppCompatActivity() {

   private lateinit var binding: ActivityMainBinding
   
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      binding = ActivityMainBinding.inflate(layoutInflater)
      setContentView(binding.root)
      binding.button.setOnClickListener {
         onClickButtons()
      }
   }
   
   fun onClickButtons() {
      Toast.makeText(
      this, 
      "Здесь обработка клика", 
      Toast.LENGTH_SHORT).show();
   }
}


6. Jetpack compose

28 июля 2021 года - по-настоящему революционная дата для Android-разработчиков. В этот день сменился полностью подход работы со View-слоем приложения. Если во всех вышеназванных способах суть заключалась в различных попытках связать XML-код и Java/Kotlin код, то вышедший в релизной версии Jetpack Compose предлагает декларативную верстку UI прямо в Kotlin-классах. И этот тот подход, какой определенно вытеснит в будущем XML.

class MainActivity : ComponentActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)    
       setContent {
               Example("Android")    
       }
   }
}
    
@Composable 
fun Example(name: String) {
   val ctx = LocalContext.current 
   Button(onClick = {

      onClickButtons(ctx,"Здесь обработка клика")
      
   }, colors = ButtonDefaults.textButtonColors( 
      backgroundColor = Color.Red 
   )) { 
      Text(text = name) 
   } 
}


fun onClickButtons(ctx: Context, msg: String) {    
   Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show()
}