Skip to content

Commit

Permalink
Merge pull request #58 from PAException/feature/firebase-performance
Browse files Browse the repository at this point in the history
Added firebase performance to api requests
  • Loading branch information
PAException authored Apr 23, 2024
2 parents 50edbb3 + acfc585 commit 8f7bcec
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 133 deletions.
42 changes: 14 additions & 28 deletions lib/src/backend/api/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,31 @@ import 'package:engelsburg_planer/src/backend/api/api_response.dart';
import 'package:engelsburg_planer/src/backend/api/request_service.dart';
import 'package:engelsburg_planer/src/backend/api/requests.dart';
import 'package:engelsburg_planer/src/backend/util/query.dart';
import 'package:firebase_performance/firebase_performance.dart' show HttpMethod;
import 'package:flutter/material.dart';
import 'package:http/http.dart';

typedef Parser<T> = T Function(dynamic json);

enum HttpMethod { get, head, post, put, patch, delete }

Parser ignore = (json) {};
Parser<dynamic> json = (json) => json;

HttpMethod httpMethodFromString(String method) {
switch (method) {
case "get":
return HttpMethod.get;
return HttpMethod.Get;
case "head":
return HttpMethod.head;
return HttpMethod.Head;
case "put":
return HttpMethod.put;
return HttpMethod.Put;
case "post":
return HttpMethod.post;
return HttpMethod.Post;
case "patch":
return HttpMethod.patch;
return HttpMethod.Patch;
case "delete":
return HttpMethod.delete;
return HttpMethod.Delete;
default:
return HttpMethod.get;
return HttpMethod.Get;
}
}

Expand Down Expand Up @@ -66,8 +65,6 @@ class Request {
/// Key to cache response of request
final String? cacheId;

final void Function(RequestAnalysis)? analytics;

const Request(
this.method,
this.host,
Expand All @@ -77,7 +74,6 @@ class Request {
this.headers,
this.https,
this.cacheId,
this.analytics,
);

/// Deserialize request
Expand All @@ -90,7 +86,6 @@ class Request {
json["headers"],
json["https"] == 1,
json["cacheId"],
json["analytics"],
);

/// Serialize request
Expand All @@ -103,7 +98,6 @@ class Request {
"headers": headers,
"https": https ? 1 : 0, //Sqflite doesn't support bool
"cacheId": cacheId,
"analytics": analytics,
};

/// Get RequestBuilder
Expand Down Expand Up @@ -136,7 +130,6 @@ class RequestBuilder {
Map<String, String> _headers = {};
bool _https = false;
String? _cacheId;
void Function(RequestAnalysis)? _analytics;

/// Build the actual request
Request build() {
Expand All @@ -154,7 +147,6 @@ class RequestBuilder {
_headers,
_https,
_cacheId,
_analytics,
);
}

Expand All @@ -165,17 +157,17 @@ class RequestBuilder {
}

/// Shorthands to set http method
RequestBuilder get get => method(HttpMethod.get);
RequestBuilder get get => method(HttpMethod.Get);

RequestBuilder get head => method(HttpMethod.head);
RequestBuilder get head => method(HttpMethod.Head);

RequestBuilder get post => method(HttpMethod.post);
RequestBuilder get post => method(HttpMethod.Post);

RequestBuilder get patch => method(HttpMethod.patch);
RequestBuilder get patch => method(HttpMethod.Patch);

RequestBuilder get put => method(HttpMethod.put);
RequestBuilder get put => method(HttpMethod.Put);

RequestBuilder get delete => method(HttpMethod.delete);
RequestBuilder get delete => method(HttpMethod.Delete);

/// Set host
RequestBuilder host(String host) {
Expand Down Expand Up @@ -233,10 +225,4 @@ class RequestBuilder {
_cacheId = cacheId;
return this;
}

/// Set callback to execute analytics call
RequestBuilder analytics(void Function(RequestAnalysis) analytics) {
_analytics = analytics;
return this;
}
}
66 changes: 25 additions & 41 deletions lib/src/backend/api/request_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:engelsburg_planer/src/backend/api/request.dart';
import 'package:engelsburg_planer/src/backend/api/requests.dart';
import 'package:engelsburg_planer/src/backend/database/state/network_state.dart';
import 'package:engelsburg_planer/src/utils/global_context.dart';
import 'package:firebase_performance/firebase_performance.dart';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
Expand All @@ -33,17 +34,21 @@ class RequestService {
///
/// Will return empty body and status 0 after defined (default 5) timeout
static Future<http.Response> execute(Request request, {Duration timeout = kTimeout}) async {
var stopwatch = Stopwatch()..start();
//Set network status loading
globalContext().read<NetworkState>().update(NetworkStatus.loading);

//Set default headers if api request
if (request.host == Host.api) request.headers.addAll(defaultApiHeaders);
//Set authorization header if request is an authenticated one

//Append Hash-header if needed (modified check)
request = appendModifiedCheck(request);
var prepareTime = stopwatch.elapsedMilliseconds;

//Log performance of request
HttpMetric metric = FirebasePerformance.instance.newHttpMetric(
request.toUri().toString(),
request.method,
);
metric.start();

//Execute request
//TODO? create retry service? (callback?), exponential backoff?
Expand All @@ -52,25 +57,22 @@ class RequestService {
.onError((error, stackTrace) => http.Response("", 999))
.timeout(timeout, onTimeout: () => http.Response("", 999))
.then((response) {
stopwatch.stop();
metric.httpResponseCode = response.statusCode;
metric.responseContentType = response.headers["Content-Type"];
metric.requestPayloadSize = response.request?.contentLength;

RequestAnalysis analysis;
if (response.statusCode == 999) {
globalContext().read<NetworkState>().update(NetworkStatus.offline);
metric.responsePayloadSize = 0;
metric.stop();

analysis = RequestAnalysis.timedOut(prepareTime);
globalContext().read<NetworkState>().update(NetworkStatus.offline);
} else {
metric.responsePayloadSize = response.contentLength;
metric.stop();

//If request wasn't timed out then set network status as online
globalContext().read<NetworkState>().update(NetworkStatus.online);

analysis = RequestAnalysis(
prepareTime: prepareTime,
responseTime: stopwatch.elapsedMilliseconds - prepareTime,
responseSize: response.contentLength,
timedOut: false,
);
}
request.analytics?.call(analysis);

return response;
});
Expand All @@ -85,18 +87,20 @@ class RequestService {

//Execute request
switch (request.method) {
case HttpMethod.get:
case HttpMethod.Get:
return await http.get(uri, headers: headers); //GET
case HttpMethod.head:
case HttpMethod.Head:
return await http.head(uri, headers: headers); //HEAD
case HttpMethod.post:
case HttpMethod.Post:
return await http.post(uri, headers: headers, body: body); //POST
case HttpMethod.put:
case HttpMethod.Put:
return await http.put(uri, headers: headers, body: body); //PUT
case HttpMethod.patch:
case HttpMethod.Patch:
return await http.patch(uri, headers: headers, body: body); //PATCH
case HttpMethod.delete:
case HttpMethod.Delete:
return await http.delete(uri, headers: headers, body: body); //DELETE
default:
throw FlutterError("Unsupported http method: ${request.method}");
}
}

Expand All @@ -117,23 +121,3 @@ class RequestService {
*/
}
}

@immutable
class RequestAnalysis {
final int prepareTime;
final int? responseTime;
final int? responseSize;
final bool timedOut;

const RequestAnalysis({
required this.prepareTime,
required this.responseTime,
required this.responseSize,
required this.timedOut,
}) : assert(timedOut || responseTime != null);

const RequestAnalysis.timedOut(this.prepareTime)
: timedOut = true,
responseTime = null,
responseSize = null;
}
40 changes: 13 additions & 27 deletions lib/src/backend/api/requests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:engelsburg_planer/src/backend/api/model/dto/update_notification_
import 'package:engelsburg_planer/src/backend/api/request.dart';
import 'package:engelsburg_planer/src/backend/util/paging.dart';
import 'package:engelsburg_planer/src/backend/util/query.dart';
import 'package:engelsburg_planer/src/services/firebase/analytics.dart';
import 'package:flutter/foundation.dart';

///
Expand Down Expand Up @@ -77,15 +76,12 @@ RequestBuilder getArticles({int? date, Paging paging = const Paging(0, 20)}) =>
.get
.path(Path.article)
.params(Query.paging(paging) + Query.date(date))
.cache("articles_${paging.page}_${paging.size}_$date")
.analytics(Analytics.api.article);
.cache("articles_${paging.page}_${paging.size}_$date");

RequestBuilder getArticle(int id) => Request.builder().https.api.get.path("${Path.article}/$id")
.analytics(Analytics.api.article);
RequestBuilder getArticle(int id) => Request.builder().https.api.get.path("${Path.article}/$id");

RequestBuilder getChangedArticles(List<String> hashes) =>
Request.builder().https.api.patch.path(Path.article).jsonBody({"hashes": hashes})
.analytics(Analytics.api.article);
Request.builder().https.api.patch.path(Path.article).jsonBody({"hashes": hashes});

///
/// Substitutes
Expand All @@ -105,21 +101,18 @@ RequestBuilder getSubstitutes(
classes: classes,
teacher: teacher,
))
.cache("substitutes-$classes-$teacher")
.analytics(Analytics.api.substitute);
.cache("substitutes-$classes-$teacher");

RequestBuilder getSubstituteMessages(String substituteKey) => Request.builder()
.https
.api
.get
.path(Path.substitute.messages)
.params(Query.substitutes(substituteKey))
.cache("substitute_messages")
.analytics(Analytics.api.substitute);
.cache("substitute_messages");

RequestBuilder getSubstituteKeyHash() =>
Request.builder().https.api.get.path(Path.substitute.keyHash).cache("substitute_key_hash")
.analytics(Analytics.api.substitute);
Request.builder().https.api.get.path(Path.substitute.keyHash).cache("substitute_key_hash");

///
/// Information
Expand All @@ -131,17 +124,15 @@ RequestBuilder getTeacher(String substituteKey) => Request.builder()
.get
.path(Path.info.teacher)
.params(Query.substitutes(substituteKey))
.cache("info_teacher")
.analytics(Analytics.api.info);
.cache("info_teacher");

RequestBuilder getClasses(String substituteKey) => Request.builder()
.https
.api
.get
.path(Path.info.classes)
.params(Query.substitutes(substituteKey))
.cache("info_classes")
.analytics(Analytics.api.info);
.cache("info_classes");

///
/// Notification
Expand All @@ -152,23 +143,18 @@ RequestBuilder updateNotificationSettings(String token, Iterable<String> priorit
.api
.post
.path(Path.notificationSettings)
.jsonBody(UpdateNotificationSettingsDTO(token: token, priorityTopics: priorityTopics))
.analytics(Analytics.api.notificationSettings);
.jsonBody(UpdateNotificationSettingsDTO(token: token, priorityTopics: priorityTopics));

RequestBuilder deleteNotificationSettings(String token) =>
Request.builder().https.api.delete.path(Path.notificationSettings).param("token", token)
.analytics(Analytics.api.notificationSettings);
Request.builder().https.api.delete.path(Path.notificationSettings).param("token", token);

///
/// Other
///
RequestBuilder getEvents() => Request.builder().https.api.get.path(Path.event).cache("event")
.analytics(Analytics.api.events);
RequestBuilder getEvents() => Request.builder().https.api.get.path(Path.event).cache("event");

RequestBuilder getCafeteria() =>
Request.builder().https.api.get.path(Path.cafeteria).cache("cafeteria")
.analytics(Analytics.api.cafeteria);
Request.builder().https.api.get.path(Path.cafeteria).cache("cafeteria");

RequestBuilder getSolarSystem() =>
Request.builder().https.api.get.path(Path.solarSystem).cache("solar_system")
.analytics(Analytics.api.solar);
Request.builder().https.api.get.path(Path.solarSystem).cache("solar_system");
Loading

0 comments on commit 8f7bcec

Please sign in to comment.