décembre 21, 2025
15 min de lecture

Questions d'entretien pour développeur mobile junior (Android) : Guide complet

interview
career-advice
job-search
entry-level
Questions d'entretien pour développeur mobile junior (Android) : Guide complet
MB

Milad Bonakdar

Auteur

Maîtrisez les fondamentaux du développement Android grâce à des questions d'entretien essentielles couvrant Kotlin, les Activités, les Fragments, Jetpack Compose, la persistance des données et les meilleures pratiques Android pour les développeurs juniors.


Introduction

Le développement Android alimente des milliards d'appareils dans le monde, ce qui en fait l'une des plateformes mobiles les plus importantes. Avec Kotlin comme langage privilégié et des outils modernes comme Jetpack Compose, les développeurs Android créent des applications riches et performantes pour divers utilisateurs.

Ce guide couvre les questions d'entretien essentielles pour les développeurs Android Junior. Nous explorerons les fondamentaux de Kotlin, les composants Android, le développement d'interface utilisateur, la persistance des données, la mise en réseau et les meilleures pratiques pour vous aider à vous préparer à votre premier rôle de développeur Android.


Fondamentaux de Kotlin (6 Questions)

1. Quelle est la différence entre val et var en Kotlin ?

Réponse :

  • val : Déclare une variable immuable (en lecture seule). Une fois attribuée, sa valeur ne peut pas être modifiée.
  • var : Déclare une variable mutable. Sa valeur peut être modifiée après l'initialisation.
  • Meilleure pratique : Utilisez val par défaut pour plus de sécurité. N'utilisez var que lorsque vous devez réassigner la valeur.
val name = "John"  // Ne peut pas être modifié
var age = 25       // Peut être modifié
age = 26           // Valide
// name = "Jane"   // Erreur : Val ne peut pas être réassigné

Rareté : Très courant Difficulté : Facile


2. Expliquez les types Nullable et l'opérateur Safe Call en Kotlin.

Réponse : Le système de types de Kotlin distingue les types nullable et non-nullable pour éviter les exceptions de pointeur null.

  • Type Nullable : Ajoutez ? après le type : String?
  • Safe Call (?.) : Appelle une méthode uniquement si l'objet n'est pas null
  • Opérateur Elvis (?:) : Fournit une valeur par défaut si null
  • Assertion Not-null (!!) : Lance une exception si null (à utiliser avec parcimonie)
var email: String? = "[email protected]"

// Safe call
val length = email?.length  // Renvoie null si email est null

// Opérateur Elvis
val displayEmail = email ?: "No email"

// Fonction Let
email?.let {
    println("Email: $it")
}

// Assertion Not-null (dangereux)
val len = email!!.length  // Lance une exception si email est null

Rareté : Très courant Difficulté : Facile


3. Quelle est la différence entre une class et une data class en Kotlin ?

Réponse :

  • Classe régulière : Définition de classe standard
  • Data Class : Génère automatiquement des méthodes utiles pour contenir des données
    • equals() et hashCode()
    • toString()
    • Fonction copy()
    • Fonctions componentN() pour la déstructuration
// Classe régulière
class Person(val name: String, val age: Int)

// Data class
data class User(val name: String, val age: Int)

val user1 = User("John", 25)
val user2 = User("John", 25)

println(user1 == user2)  // true (equals() généré)
println(user1)           // User(name=John, age=25) (toString() généré)

// Copie avec modifications
val user3 = user1.copy(age = 26)

// Déstructuration
val (name, age) = user1

Rareté : Très courant Difficulté : Facile


4. Que sont les expressions Lambda et les fonctions d'ordre supérieur en Kotlin ?

Réponse :

  • Lambda : Fonction anonyme qui peut être passée comme une valeur
  • Fonction d'ordre supérieur : Fonction qui prend des fonctions comme paramètres ou renvoie une fonction
// Expression Lambda
val sum = { a: Int, b: Int -> a + b }
println(sum(5, 3))  // 8

// Fonction d'ordre supérieur
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

val result = calculate(10, 5) { x, y -> x * y }
println(result)  // 50

// Opérations sur les collections
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }

println(doubled)  // [2, 4, 6, 8, 10]
println(evens)    // [2, 4]

Rareté : Très courant Difficulté : Moyenne


5. Expliquez les fonctions d'extension en Kotlin.

Réponse : Les fonctions d'extension vous permettent d'ajouter de nouvelles fonctions aux classes existantes sans modifier leur code source.

// Fonction d'extension pour String
fun String.isPalindrome(): Boolean {
    return this == this.reversed()
}

println("radar".isPalindrome())  // true
println("hello".isPalindrome())  // false

// Fonction d'extension avec paramètres
fun Int.times(action: () -> Unit) {
    repeat(this) {
        action()
    }
}

3.times {
    println("Hello")
}
// Affiche "Hello" trois fois

Rareté : Courant Difficulté : Facile


6. Quelle est la différence entre == et === en Kotlin ?

Réponse :

  • == : Égalité structurelle (compare les valeurs en utilisant equals())
  • === : Égalité référentielle (compare les références mémoire)
val str1 = "Hello"
val str2 = "Hello"
val str3 = str1

println(str1 == str2)   // true (même valeur)
println(str1 === str2)  // true (optimisation du pool de chaînes)
println(str1 === str3)  // true (même référence)

val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)

println(list1 == list2)   // true (même contenu)
println(list1 === list2)  // false (objets différents)

Rareté : Courant Difficulté : Facile


Composants Android (5 Questions)

7. Qu'est-ce qu'une Activity et expliquez son cycle de vie.

Réponse : Une Activity représente un seul écran avec une interface utilisateur. Elle a un cycle de vie bien défini :

Loading diagram...
  • onCreate() : L'Activity est créée. Initialisez l'interface utilisateur, définissez la vue de contenu.
  • onStart() : L'Activity devient visible.
  • onResume() : L'Activity est au premier plan et interactive.
  • onPause() : L'Activity perd le focus (une autre activité passe au premier plan).
  • onStop() : L'Activity n'est plus visible.
  • onDestroy() : L'Activity est détruite.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Initialiser les vues, configurer les données
    }
    
    override fun onStart() {
        super.onStart()
        // L'Activity devient visible
    }
    
    override fun onResume() {
        super.onResume()
        // L'Activity est interactive, démarrer les animations
    }
    
    override fun onPause() {
        super.onPause()
        // L'Activity perd le focus, mettre en pause les actions en cours
    }
}

Rareté : Très courant Difficulté : Facile


8. Quelle est la différence entre une Activity et un Fragment ?

Réponse :

  • Activity : Représente un écran complet. Point d'entrée pour l'interaction utilisateur. A son propre cycle de vie.
  • Fragment : Portion réutilisable de l'interface utilisateur au sein d'une Activity. Plusieurs fragments peuvent exister dans une seule activité. A son propre cycle de vie lié à l'activité hôte.
  • Avantages des Fragments :
    • Réutilisabilité à travers les activités
    • Composants d'interface utilisateur modulaires
    • Prise en charge des tablettes (dispositions multi-panneaux)
    • Intégration du composant de navigation
class MyFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_my, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Initialiser les vues
    }
}

// Dans Activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        supportFragmentManager.beginTransaction()
            .replace(R.id.fragment_container, MyFragment())
            .commit()
    }
}

Rareté : Très courant Difficulté : Facile


9. Qu'est-ce qu'un Intent et quels sont ses types ?

Réponse : Intent est un objet de messagerie utilisé pour demander une action à un autre composant d'application.

  • Intent explicite : Spécifie le composant exact à démarrer (par nom de classe)
  • Intent implicite : Déclare une action générale, et le système trouve le composant approprié
// Intent explicite - démarrer une activité spécifique
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("USER_ID", 123)
startActivity(intent)

// Intent implicite - ouvrir une URL
val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(webIntent)

// Intent implicite - partager du texte
val shareIntent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "Check this out!")
}
startActivity(Intent.createChooser(shareIntent, "Share via"))

// Intent implicite - passer un appel téléphonique
val callIntent = Intent(Intent.ACTION_DIAL).apply {
    data = Uri.parse("tel:1234567890")
}
startActivity(callIntent)

Rareté : Très courant Difficulté : Facile


10. Qu'est-ce qu'un Service dans Android ?

Réponse : Un Service est un composant qui s'exécute en arrière-plan pour effectuer des opérations de longue durée sans interface utilisateur.

  • Types :
    • Foreground Service : Effectue des opérations perceptibles (lecteur de musique). Affiche une notification.
    • Background Service : Effectue des opérations qui ne sont pas directement remarquées par l'utilisateur.
    • Bound Service : Permet aux composants de s'y lier et d'interagir.
class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Effectuer un travail en arrière-plan
        Thread {
            // Opération de longue durée
            Thread.sleep(5000)
            stopSelf()
        }.start()
        
        return START_STICKY
    }
    
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

// Démarrer le service
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)

// Arrêter le service
stopService(serviceIntent)

Rareté : Courant Difficulté : Moyenne


11. Qu'est-ce qu'un BroadcastReceiver ?

Réponse : BroadcastReceiver est un composant qui répond aux annonces de diffusion à l'échelle du système.

  • Cas d'utilisation : Batterie faible, changements de connectivité réseau, SMS reçu, démarrage terminé
  • Enregistrement :
    • Statique : Dans AndroidManifest.xml (limité dans les versions Android plus récentes)
    • Dynamique : Dans le code (préféré)
class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {
            Intent.ACTION_BATTERY_LOW -> {
                // Gérer la batterie faible
            }
            ConnectivityManager.CONNECTIVITY_ACTION -> {
                // Gérer le changement de réseau
            }
        }
    }
}

// Enregistrer dynamiquement
class MainActivity : AppCompatActivity() {
    private val receiver = MyBroadcastReceiver()
    
    override fun onStart() {
        super.onStart()
        val filter = IntentFilter(Intent.ACTION_BATTERY_LOW)
        registerReceiver(receiver, filter)
    }
    
    override fun onStop() {
        super.onStop()
        unregisterReceiver(receiver)
    }
}

Rareté : Courant Difficulté : Facile


Développement d'interface utilisateur (4 Questions)

12. Quelle est la différence entre LinearLayout, RelativeLayout et ConstraintLayout ?

Réponse :

  • LinearLayout : Dispose les enfants dans une seule ligne ou colonne. Simple mais peut conduire à des dispositions imbriquées.
  • RelativeLayout : Positionne les enfants les uns par rapport aux autres ou au parent. Plus flexible mais complexe.
  • ConstraintLayout : Disposition moderne et flexible. Hiérarchie de vues plate. Recommandé pour les interfaces utilisateur complexes.
<!-- Exemple ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Rareté : Très courant Difficulté : Facile


13. Qu'est-ce que RecyclerView et comment fonctionne-t-il ?

Réponse : RecyclerView est un widget efficace pour afficher de grandes listes en recyclant les vues.

  • Composants :
    • Adapter : Lie les données aux vues
    • ViewHolder : Détient des références aux vues (évite les appels findViewById)
    • LayoutManager : Positionne les éléments (Linear, Grid, Staggered)
// Data class
data class User(val name: String, val email: String)

// ViewHolder
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val nameText: TextView = itemView.findViewById(R.id.nameText)
    private val emailText: TextView = itemView.findViewById(R.id.emailText)
    
    fun bind(user: User) {
        nameText.text = user.name
        emailText.text = user.email
    }
}

// Adapter
class UserAdapter(private val users: List<User>) : 
    RecyclerView.Adapter<UserViewHolder>() {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(users[position])
    }
    
    override fun getItemCount() = users.size
}

// Dans Activity
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = UserAdapter(userList)

Rareté : Très courant Difficulté : Moyenne


14. Qu'est-ce que Jetpack Compose ?

Réponse : Jetpack Compose est la boîte à outils d'interface utilisateur déclarative moderne d'Android.

  • Déclaratif : Décrivez à quoi l'interface utilisateur devrait ressembler, pas comment la construire
  • Avantages : Moins de code, intuitif, puissant, accélère le développement
  • Fonctions composables : Éléments de base de l'interface utilisateur Compose
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

@Preview
@Composable
fun PreviewCounter() {
    Counter()
}

Rareté : Très courant Difficulté : Facile


15. Quelle est la différence entre match_parent et wrap_content ?

Réponse : Ce sont des paramètres de disposition qui définissent les dimensions de la vue :

  • match_parent : La vue s'étend pour remplir la taille du parent
  • wrap_content : La vue se dimensionne pour s'adapter à son contenu
  • Taille fixe : Valeur dp spécifique (par exemple, 100dp)
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <!-- Remplit la largeur du parent, encapsule la hauteur du contenu -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Title" />
    
    <!-- Encapsule les deux dimensions -->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me" />
    
    <!-- Taille fixe -->
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/icon" />
</LinearLayout>

Rareté : Très courant Difficulté : Facile


Données et mise en réseau (5 Questions)

16. Comment faites-vous une requête réseau dans Android ?

Réponse : Utilisez des bibliothèques comme Retrofit ou OkHttp pour la mise en réseau. Évitez d'utiliser HttpURLConnection directement.

// Configuration Retrofit
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: Int): User
    
    @POST("users")
    suspend fun createUser(@Body user: User): User
}

object RetrofitClient {
    private const val BASE_URL = "https://api.example.com/"
    
    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

// Utilisation dans ViewModel
class UserViewModel : ViewModel() {
    fun fetchUser(userId: Int) {
        viewModelScope.launch {
            try {
                val user = RetrofitClient.apiService.getUser(userId)
                // Mettre à jour l'interface utilisateur
            } catch (e: Exception) {
                // Gérer l'erreur
            }
        }
    }
}

Rareté : Très courant Difficulté : Moyenne


17. Qu'est-ce que Room et comment l'utilisez-vous ?

Réponse : Room est une couche d'abstraction au-dessus de SQLite pour un accès plus facile à la base de données.

  • Composants :
    • Entity : Représente une table
    • DAO (Data Access Object) : Définit les opérations de base de données
    • Database : Conteneur de base de données
// Entity
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String
)

// DAO
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): List<User>
    
    @Query("SELECT * FROM users WHERE id = :userId")
    fun getUserById(userId: Int): User?
    
    @Insert
    suspend fun insertUser(user: User)
    
    @Delete
    suspend fun deleteUser(user: User)
    
    @Update
    suspend fun updateUser(user: User)
}

// Base de données
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

Rareté : Très courant Difficulté : Moyenne


18. Qu'est-ce que SharedPreferences ?

Réponse : SharedPreferences stocke de petites quantités de données primitives sous forme de paires clé-valeur.

  • Cas d'utilisation : Paramètres utilisateur, préférences, indicateurs simples
  • Pas pour : Les grandes données, les objets complexes (utilisez plutôt Room)
// Sauvegarder les données
val sharedPref = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
    putString("username", "John")
    putInt("age", 25)
    putBoolean("isLoggedIn", true)
    apply()  // ou commit()
}

// Lire les données
val username = sharedPref.getString("username", "Guest")
val age = sharedPref.getInt("age", 0)
val isLoggedIn = sharedPref.getBoolean("isLoggedIn", false)

// Supprimer les données
with(sharedPref.edit()) {
    remove("username")
    apply()
}

// Effacer tout
with(sharedPref.edit()) {
    clear()
    apply()
}

Rareté : Très courant Difficulté : Facile


19. Quelle est la différence entre apply() et commit() dans SharedPreferences ?

Réponse : Les deux enregistrent les modifications dans SharedPreferences, mais diffèrent dans leur comportement :

  • apply() : Asynchrone. Renvoie immédiatement. Les modifications sont écrites sur le disque en arrière-plan. Aucune valeur de retour.
  • commit() : Synchrone. Bloque jusqu'à ce que les modifications soient écrites. Renvoie un booléen (succès/échec).
  • Meilleure pratique : Utilisez apply() sauf si vous avez besoin de la valeur de retour.
val editor = sharedPref.edit()

// apply() - asynchrone, préféré
editor.putString("key", "value")
editor.apply()  // Renvoie immédiatement

// commit() - synchrone
editor.putString("key", "value")
val success = editor.commit()  // Attend l'écriture, renvoie un booléen
if (success) {
    // Enregistrement réussi
}

Rareté : Courant Difficulté : Facile


20. Que sont les Coroutines en Kotlin ?

Réponse : Les coroutines offrent un moyen d'écrire du code asynchrone de manière séquentielle, ce qui le rend plus facile à lire et à maintenir.

  • Avantages : Léger, concurrence structurée, gestion des exceptions
  • Concepts clés :
    • suspend : Fonction qui peut être mise en pause et reprise
    • launch : Démarre une coroutine (lancer et oublier)
    • async : Démarre une coroutine et renvoie un résultat
    • Dispatchers : Contrôle sur quel thread la coroutine s'exécute
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            try {
                // Thread principal
                showLoading()
                
                // Basculer vers le thread IO pour l'appel réseau
                val data = withContext(Dispatchers.IO) {
                    fetchDataFromNetwork()
                }
                
                // Retour au thread principal
                updateUI(data)
            } catch (e: Exception) {
                handleError(e)
            }
        }
    }
    
    suspend fun fetchDataFromNetwork(): String {
        delay(2000)  // Simule un délai réseau
        return "Data from server"
    }
}

// Exécution parallèle
viewModelScope.launch {
    val deferred1 = async { fetchUser(1) }
    val deferred2 = async { fetchUser(2) }
    
    val user1 = deferred1.await()
    val user2 = deferred2.await()
}

Rareté : Très courant Difficulté : Moyenne

Newsletter subscription

Conseils de carrière hebdomadaires qui fonctionnent vraiment

Recevez les dernières idées directement dans votre boîte de réception

Decorative doodle

Arrêtez de Postuler. Commencez à Être Embauché.

Transformez votre CV en un aimant à entretiens avec l'optimisation alimentée par l'IA en laquelle les chercheurs d'emploi du monde entier font confiance.

Commencer gratuitement

Partager cet article

Doublez Vos Rappels d'Entretien

Les candidats qui adaptent leur CV à la description du poste obtiennent 2,5 fois plus d'entretiens. Utilisez notre IA pour personnaliser automatiquement votre CV pour chaque candidature instantanément.