Title: An Empirical Catalog of Code Smells for the Presentation Layer of Android Apps.

Authors: Suelen Goularte Carvalho, Maurício Aniche, Júlio Veríssimo, Rafael S. Durelli, and Marco Aurélio Gerosa ·

Abstract: Developers frequently seek code to be refactored, searching, for example, for code smells. Although the literature presents a variety of code smells, such as God Class and Long Method, different technologies are not taken into account. When it comes to the presentation layer, Android apps are a special kind of software: they need to implement specific architectural decisions from the Android platform itself (such as modeling Activities, Fragments, and Listeners to events) as well as deal with and integrate different types of resources (such as layouts and images). We investigate what code smells developers perceive in the presentation layer of Android apps. Through a three-step study that involved 300 Android developers, we devised 20 specific code smells and collected the developers’ perceptions of their frequency and importance. We implemented a tool that automates the identification of 80% (16/20) of these smells. To validate we carried out an empirical study over the prevalence of these smells. The study was performed involving 619 open-source apps from F-Droid. Our findings suggest that: 1) there exist code smells specific to the presentation layer of Android apps; 2) developers consider these code smells to be of high importance and frequent occurrence; and 3) developers perceive classes affected by the smells as problematic when compared to clean classes. These results may help practitioners and tool developers while searching for potentially problematic pieces of code in Android Apps.


Component Smells.

Nine Component Smells are presented here.

Brain UI Component

Activities, Fragments, and Adapters should be responsible for presenting, interacting, and updating the UI only. In other words, they should be responsible for any view logic that the app might have. Executing any business logic, on the other hand, should happen somewhere else. The UI layer should not contain details that are related to the domain. The existence of code related to business logic, I/O operations, conversion of data, or static fields in these elements are signs of code smell.

Example:
public class MainActivity extends AppCompatActivity {
    // other codes here

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        CardRepositoty repository = new CardRepositoty();
        repository.checkCardLimit()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<LimitResponse>() {
                    @Override
                    public void onCompleted() {
                        dismissProgress();
                    }

                    @Override
                    public void onError(Throwable throwable) {
                        dismissProgress();
                        ErrorManager errorManager = new ErrorManager(throwable);
                        Exception exception = errorManager.parseError();
                        if (exception instanceof ForbiddenException) {
                            showForbidden();
                        } else {
                            showError(exception.getMessage());
                        }
                    }

                    @Override
                    public void onNext(LimitResponse limitResponse) {
                        limit = limitResponse.getDisponibGlobalCredito();

                        if (limit < 0) {
                            binding.limit.setText("R$ 0,00");
                        } else {
                            binding.limit.setText(new DecimalUtil().convertToReal(limit));
                        }
                    }
                });
    }

    // other codes here
}
Refactoring suggestion:

Only code related to the UI in Activities, Fragments, Adapters, and Listeners. Break all business logic into two or more classes.

Coupled UI Component

Fragments, Adapters, and Listeners, in order to be reused, should not have direct reference to who uses them. The existence of direct reference to Activities or Fragments in these elements is an evidence of code smell.

Example:
public class CloudSheetFragment extends BottomSheetDialogFragment {
    // other codes here

    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);

        rootView = getActivity().getLayoutInflater().inflate(R.layout.fragment_sheet_cloud, null);

        if (((MainActivity) getActivity()).getAppTheme().equals(AppTheme.DARK)) {
            rootView.setBackgroundColor(Utils.getColor(getContext(), R.color.holo_dark_background));
        } else {
            rootView.setBackgroundColor(Utils.getColor(getContext(), android.R.color.white));
        }

        dialog.setContentView(rootView);
    }
    // other codes here
}
Refactoring suggestion:

Make the UI components reusable. Therefore, one should avoid dependence on other components of the application. Break the UI components so they are reusable.

Suspicious Behavior

Activities, Fragments, and Adapters should not contain, in its source code, the implementation of their event handlers. First, event handling code, when embedded into one of aforementioned components, is implemented through anonymous or internal classes. As the interfaces that these event handlers need to implement are often complex, the source code of the Activities, Fragments, and Adapters becomes less readable. Second, an event handler often makes use of the business rules and the domain model of the app. A less attentive developer may then write these business rules directly into the event handler (which then leads us to a possible Brain UI Component smell). The use of anonymous classes or internal classes to implement Listeners to respond to user events is a sign of code smell.

Example:
public class SettingsActivity extends AppCompatActivity {
    // other codes here

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        binding.currencySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean bChecked) {
                if (bChecked) {
                    binding.currencySwitch.setChecked(true);
                    editor.putString("currency", "USD");
                    editor.commit();
                } else {
                    binding.currencySwitch.setChecked(false);
                    editor.putString("currency", "EUR");
                    editor.commit();
                }
            }
        });

        binding.periodInput.setText(this.period);
        binding.periodInput.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) { }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

            @Override
            public void afterTextChanged(Editable s) {
                period = binding.periodInput.getText().toString();
                if (period.equals("")) {
                    editor.putString("period", "30");
                    editor.commit();
                } else {
                    editor.putString("period", period);
                    editor.commit();
                }
            }
        });

        TextView aboutText = (TextView) findViewById(R.id.about);
        aboutText.setMovementMethod(LinkMovementMethod.getInstance());
    }
    // other codes here
}
Refactoring suggestion:

Prefer to state the listeners with implements and overwrite methods than to make one set in the listener object itself.

Fool Adapter

This smell is present when Adapters do not reuse instances of the views that represent the fields that will be populated for each item of a collection by means of the View Holder pattern.

Example:
public class ProfileAdapter extends ArrayAdapter<User> {
    // other codes here

    public DrawerAdapter(Context context, ArrayList<User> users) {
        // ...
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view = inflater.inflate(R.layout.profile_item, parent, false);
        TextView txtName = (TextView) view.findViewById(R.id.name);
        TextView txtSurname = (TextView) view.findViewById(R.id.surname);
        TextView txtEmail = (TextView) view.findViewById(R.id.email);
        ImageView imageView = (ImageView) view.findViewById(R.id.icon);

        User user = (User) getItem(position);
        txtName.setText(item.getName());
        txtSurname.setText(item.getSurname());
        txtEmail.setText(item.getEmail());
        imageView.setImageDrawable(item.getIcon());
        imageView.clearColorFilter();

        // ...

        return view;
    }
    // other codes here
}
Refactoring suggestion:

It is a good practice to use the View Holder Pattern.

Absence of an Architecture

This smell emerges when one cannot easily identify how the components are organized. Developers cannot identify whether the application makes use of Model View Controller (MVC), Model View Presenter (MVP), or Model View ViewModel (MVVM).

Example:
public class MainMenuActivity extends BaseActivity {
    // other codes here

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main_menu);

        if (getIntent().getExtras() != null) {
            visitorId = getIntent().getExtras().getInt(Constants.RegisterVisitor.ARGUMENT_VISITOR_ID);
        }

        setupToolbar();
        setListeners();
        if (!hasGpsPermissionGranted) {
            doLogin();
        }
        // ...
    }

    @Override
    protected void onResume() {
        super.onResume();
        name = PreferencesUtil.getString(this, Constants.NAME, "");
        isVisitor = PreferencesUtil.isVisitor(binding.getRoot().getContext());

        user = PreferencesUtil.getUser(this);
        if (user != null && !isVisitor) {
            doLimit();
        }

        if (!TextUtils.isEmpty(name) && !isVisitor) {
            String[] nameSplit = name.split(Constants.WHITESPACE);
            name = nameSplit[0] + Constants.WHITESPACE + nameSplit[1];
            binding.includeAccountDetails.textviewUserName.setText(name);
        } else {
            binding.includeAccountDetails.textviewUserName.setText(R.string.visitor_default_name);
        }

        // ...
    }

    private void doLogin() {
        final Intent intent = getIntent();
        if (intent.hasExtra(Constants.SharedPreferences.CPF) && intent.hasExtra(Constants.SharedPreferences.PASSWORD)) {
            userCpf = intent.getStringExtra(Constants.SharedPreferences.CPF);
            userPassword = intent.getStringExtra(Constants.SharedPreferences.PASSWORD);
            loginData = createLoginData(userCpf, userPassword);

            showProgress(context.getString(R.string.dialog_message_do_login));
            repository.doLogin(loginData)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<LoginResponse>() {
                    @Override
                    public void onCompleted() {}

                    @Override
                    public void onError(Throwable throwable) {
                        dismissProgress();
                        ErrorManager errorManager = new ErrorManager(throwable);
                        Exception exception = errorManager.parseError();
                        if (exception instanceof ForbiddenException) {
                            showForbidden();
                        } else {
                            showError(exception.getMessage());
                        }
                    }

                    @Override
                    public void onNext(LoginResponse loginResponse) {
                        User user = loginResponse.getUser();
                        if (user != null) {
                            saveUser(loginResponse.getUser(), loginData.getPassword());
                        }
                    }
                });
        } else {
            loadSettings();
        }
    }

    public void checkLimitResponseSuccess(LimitResponse limitResponse) {
        limit = limit < 0 ? 0 : limitResponse.getValue();
        binding.includeAccountDetails.textviewBalanceValue.setText(CurrencyUtil.convertToCurrency(String.valueOf(getlimit())));
    }

    // other codes here
}
Refactoring suggestion:

Try to break all artifacts in order to follow at least one architecture such as: Model View Controller (MVC), Model View Presenter (MVP), or Model View ViewModel (MVVM)

Excessive Use of Fragments

This smell emerges when Fragments are used without an explicit need. Examples are applications that do not (need not to) support tablets, or when Fragments are used in only a single screen of the app.

Refactoring suggestion:

It is not a good practice to use a lot of Fragments. Transform some Fragments into Activities and try to use Fragments just with View Pagers.

UI Component Doing I/O

Activities, Fragments, and Adapters performing I/O operations, such as database and file access, cause this smell.

Example:
public class MainActivity extends AppCompatActivity {
    // other codes here

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView aboutText = (TextView) findViewById(R.id.about);
        aboutText.setMovementMethod(LinkMovementMethod.getInstance());

        User user = userRepository.getUser();

        SharedPreferences sharedPreferences = activity.getSharedPreferences(SHARED_TUTO, Context.MODE_PRIVATE);
        sharedPreferences.edit().putString(USER_NAME, user.getName()).apply();

        // ...
    }

    // other codes here
}
Refactoring suggestion:

Remove all I/O direct access from Activities, Fragments, and Adapters.

No Use of Fragments

Fragments can decouple UI with behavior pieces. The non use of Fragments can be a smell in apps that are visually rich. Such apps contain a high number of different behaviors, animations, and events to handle. If all the implementation complexity relies on a single Activity, for example, this class will be highly complex and hard to understand. Moreover, visually rich apps are also often responsive, i.e., have different UIs for different screen sizes. In this case, not using Fragments will hinder code reuse. This code smell emerges when view components, e.g., EditTexts or Spinners, are directly used by an Activity instead of Fragment.

Refactoring suggestion:

Remove direct access to view components in Activity. Create Fragments to decouple and improve the reuse all view components.

Flex Adapter

Adapters should be responsible for populating a view from a single object. The code smell emerges when Adapters contains logic and make decisions (ifs or switchs) or calculations in the method responsible to load the view.

Example:
public class CityWeatherAdapter extends RecyclerView.Adapter<CityWeatherAdapter.ViewHolder> {
    // other codes here

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view;
        if (viewType == OVERVIEW) {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_overview, viewGroup, false);
            return new OverViewHolder(v);

        } else if (viewType == DETAILS) {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_details, viewGroup, false);
            return new DetailViewHolder(v);

        } else if (viewType == WEEK) {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_week, viewGroup, false);
            return new WeekViewHolder(v);
        }
        // ...
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {

        if (viewHolder.getItemViewType() == OVERVIEW) {
            OverViewHolder holder = (OverViewHolder) viewHolder;
            // ...

        } else if (viewHolder.getItemViewType() == DETAILS) {
            DetailViewHolder holder = (DetailViewHolder) viewHolder;
            // ...

        } else if (viewHolder.getItemViewType() == WEEK) {
            WeekViewHolder holder = (WeekViewHolder) viewHolder;
            // ...

        }
        // ...
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View v) {
            super(v);
        }
    }

    public class OverViewHolder extends ViewHolder {
        TextView temperature;
        ImageView weather;

        public OverViewHolder(View v) {
            super(v);
            this.temperature = (TextView) v.findViewById(R.id.activity_city_weather_temperature);
            this.weather = (ImageView) v.findViewById(R.id.activity_city_weather_image_view);
        }
    }

    // others ViewHolders and codes here
}
Refactoring suggestion:

An Adapter must adapt a single type of item or delegate to specialized Adapters -- remove business logic and business decisions of Adapter.


Resource Smells

Here are presented 11 Resource Smells.

No Naming Pattern

This smell happens when resources (layout, string, style, drawables) do not follow a naming pattern. More specifically, it happens when the file where the resource is located and its internal name (i.e., how the resource is called inside the source code) are different. These different names are a cause of confusion among developers.

Refactoring suggestion:

Start the name of a string with the name of the screen where it will be used. It is important to have a good naming convention.

Missing Image

This code smell happens when the system contains only a single version of its .png, .jpg, or .gif images. The Android platform requires images to be available in more than one size or resolution to perform optimizations.

Refactoring suggestion:

Create folders for various resolutions and put the correct images.

Magic Resource

A smell that results when resources (e.g., layout, string, and style) are hard-coded instead of pointing to an existing resource.

Example:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txt_warning_message"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="Ops! Seens that you don't have any item to show yet!" />

</RelativeLayout>
Refactoring suggestion:

Remove all embedded strings and colors from android:text and android:.*Color.* attribute, respectively. Then, move all string to res/values/strings.xml and move all colors to res/values/colors.xml. After, reference all the strings and colors into the layout.xml.

Deep Nested Layout

Deep nesting when constructing layout resources was considered a code smell.

Example:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/main_tool_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/activity_toolbar_height"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true" >

        <HorizontalScrollView
            android:layout_width="match_parent"
            android:layout_height="@dimen/activity_toolbar_height"
            android:layout_alignParentTop="true"
            android:background="?attr/colorPrimary"
            android:paddingBottom="@dimen/activity_palette_vertical_padding"
            android:paddingTop="@dimen/activity_palette_vertical_padding" >

            <LinearLayout
                android:id="@+id/main_palette_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal" >

                <LinearLayout
                    style="@style/DialogTextEditBackgroundStyle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content">
                    <com.mkulesh.micromath.widgets.CustomEditText
                        android:id="@+id/hidden_edit_text"
                        style="@style/DialogTextEditStyle"
                        android:layout_width="@dimen/activity_toolbar_height"
                        android:layout_height="@dimen/activity_hidden_text_height"
                        android:paddingTop="3dp" />
                </LinearLayout>

            </LinearLayout>
        </HorizontalScrollView>

        <!-- more code here -->
    </RelativeLayout>
</RelativeLayout>
Refactoring suggestion:

Remove hierarchies from long views, that is, deeply nested structures should not exist.

Unnecessary Image

Android has resources that can replace images. The smell emerges when the system has images with, for example, pure solid colors or gradients, which could be replaced by Android’s native shapes.

Refactoring suggestion:

Using .jpg or .png for simple shapes is bad, just draw them through Drawable Resources.

Long or Repeated Layout

The code smell emerges when long or duplicated layout resources (instead of good reuse) occur in the source code.

Example:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
      android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/txt_name"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="Nome completo:" />

        <EditText
            android:id="@+id/edt_name"
            android:layout_width="match_parent"
            android:layout_height="40dp" />
    </RelativeLayout>

    <RelativeLayout
      android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/txt_surname"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="Nome completo:" />

        <EditText
            android:id="@+id/edt_surname"
            android:layout_width="match_parent"
            android:layout_height="40dp" />
    </RelativeLayout>

    <RelativeLayout
      android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/txt_email"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="Nome completo:" />

        <EditText
            android:id="@+id/edt_email"
            android:layout_width="match_parent"
            android:layout_height="40dp" />
    </RelativeLayout>

    <!-- more code here -->
</RelativeLayout>
Refactoring suggestion:

Add include statements. It is a good practice to use includes for some similar layout piece

God Style Resource

Long style resources. Symptoms of this smell happen when all styles are defined in the same styles.xml.

Refactoring suggestion:

If possible, separate more than the standard styles.xml file, since you can declare multiple style XML files for the same configuration. Divide all styles. Themes and styles is a rational choice.

God String Resource

Long string resources. Developers should separate their string resources according to some rule, e.g., one string resource per screen.

Refactoring suggestion:

All Activity should have a specific .XML file to manage its strings. Therefore, for each Activity in an App create a file into res/values/string*.xml.

Inappropriate String Reuse

Developers reuse strings among the different UIs of the application. For example, the string “Name” might appear in many parts of the app; thus, developers write this string only once in a string resource file, and reuse it whenever they need it. However, the smell happens when developers reuse the same string in different parts of the system just because the string is coincidentally the same, and not because they represent the same thing in the UI. For example, in one part of the app, “name” might refer to the name of the user, whereas in another part of the app, “name” might refer to the name of the user’s favourite band. Reusing strings just because of their similarity might lead to two problems: First, if the developer decides to change the string, s/he needs to be aware that the changes will be reflected throughout the entire application (and might not be what s/he wants). Second, when adding support for multiple languages, a language might need two different words to express what another language might need just one (thus, making internationalization harder).

Refactoring suggestion:

All screen should have its own string resource. Therefore, for each screen in a App create a file into res/values/name.xml.

Duplicate Style Attributes

Android developers often choose to define the style of an UI element directly into the layout file. However, this might lead to unnecessary duplication (e.g., the same complex style appears in different components). The existence of duplicated style definitions in different components is an indication of this code smell.

Example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/widget_margin"
    android:orientation="horizontal"
    android:weightSum="3"
    android:background="@drawable/rounded_corner"
    android:previewImage="@drawable/weather_widget_preview"
    android:minHeight="40dp"
    android:minWidth="250dp">

    <ImageView
        android:id="@+id/widget_city_weather_image_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:scaleType="centerCrop"
        android:src="@mipmap/weather_icon_sunny_with_clouds"
        android:layout_weight="2"/>

    <GridLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:orientation="vertical"
        android:columnCount="4"
        android:layout_weight="1"
        android:paddingStart="11dp">

        <TextView
            android:id="@+id/widget_city_name"
            android:layout_width="wrap_content"
            android:text="CityName"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="18dp"
            android:layout_column="0"
            android:layout_row="0"
            android:layout_columnSpan="4"
            android:textStyle="bold"
            android:textColor="@color/black"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="Temp:"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="0"
            android:layout_row="1"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/widget_city_weather_temperature"
            android:layout_width="wrap_content"
            android:text="-273,15°C"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="1"
            android:layout_row="1"
            android:layout_gravity="end"
            android:textColor="@color/black"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="Hum:"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="0"
            android:layout_row="2"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/widget_city_weather_humidity"
            android:layout_width="wrap_content"
            android:text="42%"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="1"
            android:layout_row="2"
            android:layout_gravity="end"
            android:textColor="@color/black"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="Rise:"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="2"
            android:layout_row="1"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/widget_city_weather_rise"
            android:layout_width="wrap_content"
            android:text="06:00"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="3"
            android:layout_row="1"
            android:textColor="@color/black"/>
        <TextView
            android:layout_width="wrap_content"
            android:text="Set:"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="2"
            android:layout_row="2"
            android:layout_gravity="end"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/widget_city_weather_set"
            android:layout_width="wrap_content"
            android:text="18:00"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="3"
            android:layout_row="2"
            android:textColor="@color/black"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="Wind:"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="0"
            android:layout_row="3"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/widget_city_weather_wind"
            android:layout_width="wrap_content"
            android:text="42 km/h"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="11dp"
            android:textSize="12dp"
            android:layout_column="1"
            android:layout_row="3"
            android:layout_gravity="end"
            android:textColor="@color/black"/>

    </GridLayout>
</LinearLayout>
Refactoring suggestion:

Remove all duplicate style attributes.

Hidden Listener

Layout resources should only be responsible for presenting data. The smell appears when these resources also configure the listener that will respond to its events, such as onClick. Such decisions make it harder for developers to identify which listeners are used.

Example:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txt_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Email:" />

    <EditText
        android:id="@+id/edt_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/button_confirm"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button_positive_drawable"
        android:text="@string/ok"
        android:onClick="subscribe" />

</RelativeLayout>
Refactoring suggestion:

Remove the View (Button, TextView, CheckBox, etc) element that contains the android:onClick attribute in the XML and add setOnClickListener in the corresponding Activity or Fragment.