Sobes.tech
Middle+

Tell us about examples of anti-patterns in Android development and how to avoid them.

sobes.tech AI

Answer from AI

Replacing findViewById with the view binding library.

<!-- Bad: manual search -->
<TextView
    android:id="@+id/myTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
// Bad: findViewById
val textView: TextView = findViewById(R.id.myTextView)
textView.text = "Hello"
// Good: in build.gradle (enabling view binding)
android {
    buildFeatures {
        viewBinding true
    }
}
// Good: using view binding
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
    binding.myTextView.text = "Hello"
}

Using a singleton to store UI state.

// Bad: singleton for UI state
object AppState {
    var isLoggedIn: Boolean = false // Global state
}

class LoginActivity : AppCompatActivity() {
    // Using AppState in Activity
}
// Good: using ViewModel
class LoginViewModel : ViewModel() {
    private val _isLoggedIn = MutableLiveData<Boolean>()
    val isLoggedIn: LiveData<Boolean> get() = _isLoggedIn

    fun login(username: String, password: String) {
        // Login logic
        _isLoggedIn.value = true // Updating state in ViewModel
    }
}

class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Observing ViewModel
        viewModel.isLoggedIn.observe(this, Observer { isLoggedIn ->
            // Updating UI based on isLoggedIn
        })
    }
}

Direct access to View from business logic or other layers.

// Bad: direct access to View from logic class
class LoginManager {
    fun performLogin(username: String, password: String, statusTextView: TextView) {
        // Logic
        statusTextView.text = "Login successful" // Direct View update
    }
}
// Good: using Model-View-ViewModel (MVVM)
class LoginViewModel : ViewModel() {
    private val _status = MutableLiveData<String>()
    val status: LiveData<String> get() = _status

    fun performLogin(username: String, password: String) {
        // Logic
        _status.value = "Login successful" // Updating LiveData
    }
}

class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel by viewModels()
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Observing ViewModel
        viewModel.status.observe(this, Observer { status ->
            binding.statusTextView.text = status // Updating View from Observer
        })
    }
}

Using context to store data or dependencies.

// Bad: storing object with Context
class AppContextHolder private constructor(private val context: Context) {
    // Storing and using Context
}
// Good: dependency injection (e.g., Dagger Hilt)
// Example with Hilt
@HiltAndroidApp
class MyApp : Application()

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
        // Using @ApplicationContext
        return Room.databaseBuilder(
            appContext,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }
}

Excessive use of static fields.

// Bad: static fields for data storage
object DataCache {
    var cachedData: List<Item>? = null // Static field
}
// Good: using repository with ViewModel
class ItemRepository {
    private var cachedData: List<Item>? = null

    fun getItems(): List<Item> {
        if (cachedData == null) {
            // Load data
        }
        return cachedData!!
    }
}

class ItemViewModel(private val repository: ItemRepository) : ViewModel() {
    val items: LiveData<List<Item>> = liveData {
        emit(repository.getItems()) // Using repository
    }
}

Lack of lifecycle component handling.

// Bad: memory leak due to unhandled listeners
class MyActivity : AppCompatActivity() {
    private val mySensorManager: MySensorManager by lazy { MySensorManager(this) }

    override fun onStart() {
        super.onStart()
        mySensorManager.startListening { data ->
            // Data handling, but listener is not removed on stop
        }
    }
}

// Class that may hold a reference to Activity
class MySensorManager(private val context: Context) { // Potential leak
    private var listener: ((String) -> Unit)? = null

    fun startListening(listener: (String) -> Unit) {
        this.listener = listener
        // Start listening
    }
}
// Good: using Scope or Flow with lifecycle
class MyActivity : AppCompatActivity() {
    private val mySensorManager: MySensorManager by lazy { MySensorManager() }

    override fun onStart() {
        super.onStart()
        lifecycleScope.launch { // Using lifecycleScope
            mySensorManager.sensorData.collect { data ->
                // Data handling
            }
        }
    }
}

// Class with Flow
class MySensorManager {
    private val _sensorData = MutableSharedFlow<String>()
    val sensorData: SharedFlow<String> = _sensorData

    init {
        // Start data collection, emit to _sensorData
    }
}
Tell us about examples of anti-patterns in Android… - sobes.tech