Intégration
Pendant les séances d'intégration, il n'y a pas de nouveau contenu. Le but est de consolider ce que tu as vu dans les séances précédentes.
Exercices à compléter.
Tu dois compléter le TP2.
Exercice complémentaire
Exercice préparatoire : Trace client-serveur
Contexte : Le code suivant met en place une interaction client-serveur entre une application Android et un serveur Spring Boot. L’objectif est de permettre l’affichage des détails d’une tâche à partir de son identifiant (id). L’interaction est initiée lorsque l’utilisateur sélectionne une tâche dans l'application.
Code client :
data class TaskDetailResponse(
val id: Long,
val name: String,
val deadline: Date,
val events: List<ProgressEvent>,
val percentageDone: Int,
val percentageTimeSpent: Double
)
interface Service {
@GET("/api/detail/{id}")
fun detail(@Path("id") id: Long) : Call<TaskDetailResponse>
}
object RetrofitUtil {
private var instance: Service? = null
fun get(): Service {
if (instance == null) {
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(CustomGson.getIt()))
.client(client())
.baseUrl("http://10.0.2.2:8080/")
.build()
instance = retrofit.create<Service>(Service::class.java)
return instance!!
} else{
return instance!!
}
}
private fun client(): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(SessionCookieJar)
.build()
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppNavigation()
}
}
}
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(navController)
}
composable("details/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")?.toLong() ?: 0
DetailsScreen(id)
}
}
}
@Composable
fun DetailsScreen(id: Long) {
var task by remember { mutableStateOf<TaskDetailResponse?>(null) }
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
LaunchedEffect(id) {
val service: Service = RetrofitUtil.get()
service.detail(id).enqueue(object : Callback<TaskDetailResponse> {
override fun onResponse(
call: Call<TaskDetailResponse>,
response: Response<TaskDetailResponse>
) {
if (response.isSuccessful) {
task = response.body()
} else {
scope.launch {
snackbarHostState.showSnackbar("Erreur serveur : ${response.code()}")
}
}
}
override fun onFailure(call: Call<TaskDetailResponse>, t: Throwable) {
scope.launch {
snackbarHostState.showSnackbar("Erreur réseau")
}
}
})
}
}
Code serveur :
@Controller
public class ControllerTask {
@Autowired
private ServiceTask serviceTask;
@GetMapping("/api/detail/{id}")
public @ResponseBody TaskDetailResponse detail(@PathVariable long id) {
System.out.println("KICKB SERVER : Detail with cookie ");
MUser user = currentUser(); // Décrire uniquement l'effet global de cette ligne
return serviceTask.detail(id, user);
}
private MUser currentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
System.out.println("Le nom utilisateur est " + username);
UserDetails ud = (UserDetails) authentication.getPrincipal();
return serviceTask.userFromUsername(ud.getUsername());
}
}
Étant données les premières étapes suivantes, produisez la trace d'exécution décrivant les principales étapes de la communication client-serveur:
- Le serveur Spring Boot est lancé localement;
- L'utilisateur démarre l'application Android;
- L'utilisateur s'inscrit et crée une première tâche. De retour à l'écran d'accueil, il sélectionne la tâche nouvellement créée pour en consulter les détails.
| ligne exécutée | effet | pile d'appels |
|---|