- Sensorki – aplikacja „pogodowa” – #0
- Sensorki – widok i adapter – #1
- Sensorki – refresh – #2
- Sensorki – material design – #3
- Sensorki – wskaźnik baterii – #4
- Sensorki – widget – #5
- Sensorki – groovy/spring boot demo serwer – #6
- Sensorki – Android i Spock – #7
- Sensorki – podsumowanie – #8
- Sensorki – Serverless app i refaktoring – #9
Postanowiłam zmienić format jsona, którego obrabiam na androidzie, na bardziej przystępny. Tak powstała aplikacja w chmurze google’a, która pozwoliła uprościć kod.
| { | |
| "data": { | |
| "hum_temp": "Temperatura (\u00b0C)", | |
| "hum_hum": "Wilgotno\u015b\u0107 (%)", | |
| "bar_pres_rel": "Ci\u015bnienie (hPa)", | |
| "lux": "Jasno\u015b\u0107 (lx)", | |
| "vbat": "Nap. baterii (V)", | |
| "vreg": "Nap. sensor\u00f3w (V)" | |
| }, | |
| "sensors": { | |
| "1": { "label": "Balkon", "data": ["hum_temp","hum_hum","bar_pres_rel","lux","vbat","vreg"] }, | |
| "2": { "label": "Salon", "data": ["hum_temp","hum_hum","lux","vbat","vreg" ] }, | |
| "4": { "label": "Biuro", "data": ["hum_temp","hum_hum","lux","vbat","vreg" ] }, | |
| "5": { "label": "\u0141azienka", "data": ["hum_temp","hum_hum","lux","vbat","vreg" ] } | |
| }, | |
| "readings": { | |
| "1": { "id": 3426091, "stamp": 1549810634, "sensor_id": 1, "seq": 45116, "status": 0, "flags": 23, "bar_temp": 8.22, "bar_pres_abs": 985.472, "bar_pres_rel": 1001.16, "lux": 293.8, "hum_temp": 7.76, "hum_hum": 111.8, "vbat": 3.76, "vreg": 2.85 }, | |
| "2": { "id": 3426089, "stamp": 1549810629, "sensor_id": 2, "seq": 31088, "status": 0, "flags": 6, "bar_temp": null, "bar_pres_abs": null, "bar_pres_rel": null, "lux": 5.72344, "hum_temp": 24.01, "hum_hum": 51.52, "vbat": 3.64, "vreg": 2.76 }, | |
| "4": { "id": 3426088, "stamp": 1549810626, "sensor_id": 4, "seq": 49955, "status": 0, "flags": 22, "bar_temp": null, "bar_pres_abs": null, "bar_pres_rel": null, "lux": 15.9223, "hum_temp": 23.38, "hum_hum": 54.3, "vbat": 3.7, "vreg": 2.84 }, | |
| "5": { "id": 3426090, "stamp": 1549810633, "sensor_id": 5, "seq": 47950, "status": 0, "flags": 22, "bar_temp": null, "bar_pres_abs": null, "bar_pres_rel": null, "lux": 0.0259, "hum_temp": 26.18, "hum_hum": 51.63, "vbat": 3.67, "vreg": 2.79 } | |
| } | |
| } |
Format JSON-a zawierający odczyty sensorka nie był zbyt przyjazny do wyświetlania przez aplikację. Trzy obiekty zawierające kolejne obiekty. Dużo danych, których wcale nie potrzebowałam. Żeby wyciągnąć etykietę z sensora musiałam się odwołać do innej listy.
Postanowiłam sprowadzić tego JSON-a do prostej listy zawierającej tylko te dane, z których naprawdę korzystała. Za pomocą javaScriptu i node.js stworzyłam funkcję pobierajcą jsona z dotychczasowego endpointu. Dwie pętle i JSON zaczął być maksymalnie użyteczny.
| const https = require("https"); | |
| const http = require("http"); | |
| http.createServer(function (request, response) { | |
| response.writeHead(200, {'Content-Type': 'application/json'}); | |
| const req = https.request('https://xxxxxxxxxxxxx.com/data.json', (res) => { | |
| console.log(`STATUS: ${res.statusCode}`); | |
| console.log(`HEADERS: ${JSON.stringify(res.headers)}`); | |
| res.setEncoding('utf8'); | |
| res.on('data', (chunk) => { | |
| const data = JSON.parse(chunk); | |
| const sensors = []; | |
| for (const id in data.readings) { | |
| const sensor = {}; | |
| const sourceSensor = data.sensors[id]; | |
| sensor.label = sourceSensor.label; | |
| const readings = data.readings[id]; | |
| for (const k of ["hum_temp", "stamp", "hum_hum", "lux", "bar_pres_rel", "vbat", "vreg", "id"]) { | |
| if (k === 'stamp') { | |
| sensor[k] = readings[k] * 1000 | |
| } else { | |
| sensor[k] = readings[k] | |
| } | |
| } | |
| sensors.push(sensor); | |
| } | |
| response.end(JSON.stringify(sensors)); | |
| }); | |
| res.on('end', () => { | |
| console.log('No more data in response.'); | |
| }); | |
| }); | |
| req.end(); | |
| }).listen(8081); |
Wyodrębniłam z serwera funkcję i przeniosłam do chmury google’a tworząc „aplikację bezserwerową.
W wyniku obróbki przez funkcję dostaję teraz takiego JSON-a.
| [ | |
| { "label": "Balkon", "id": 3426099, "hum_temp": 7.7, "stamp": 1549810672000, "hum_hum": 111.72, "lux": 291.902, "bar_pres_rel": 1001.12, "vbat": 3.75, "vreg": 2.84 }, | |
| { "label": "Salon", "id": 3426097, "hum_temp": 24, "stamp": 1549810663000, "hum_hum": 51.31, "lux": 5.74635, "bar_pres_rel": null, "vbat": 3.64, "vreg": 2.76 }, | |
| { "label": "Biuro", "id": 3426096, "hum_temp": 23.38, "stamp": 1549810661000, "hum_hum": 54.19, "lux": 15.9444, "bar_pres_rel": null, "vbat": 3.7, "vreg": 2.84 }, | |
| { "label": "Łazienka", "id": 3426098, "hum_temp": 26.17, "stamp": 1549810668000, "hum_hum": 51.62, "lux": 0.0259, "bar_pres_rel": null, "vbat": 3.67, "vreg": 2.79 } | |
| ] |
Pozwoliło mi to na użycie biblioteki Moshi do mapowania JSONa prosto do javowej klasy. Adnotacje w miejscach gdzie nazwa pola nie zgadzała się z polem w JSONie
| public class Sensor { | |
| private long id; | |
| private String label; | |
| @Json(name = "hum_temp") | |
| private double temperature; | |
| @Json(name = "stamp") | |
| private long timestamp; | |
| @Json(name = "hum_hum") | |
| private double humidity; | |
| private double lux; | |
| @Json(name = "bar_pres_rel") | |
| private Double barPressure; | |
| private double vbat; | |
| private double vreg; | |
| } |
i już mogę korzystać z tego, że serwer zwraca mi po prostu listę obiektów Sensor zamiast JSON Object
| public interface WeatherService { | |
| @GET("sensorFriendly") | |
| Call<List<Sensor>> loadData(); | |
| Retrofit retrofit = new Retrofit.Builder() | |
| .baseUrl(BuildConfig.WEATHER_ADDRESS) | |
| .addConverterFactory(MoshiConverterFactory.create()) | |
| .build(); | |
| } |
Pozbywam się ręcznego wydobywania z JSON-a labelek i poszczególnych odczytów.
| private void sendQueryForData() { | |
| final Context mContext = this; | |
| WeatherService weatherService = WeatherService.retrofit.create(WeatherService.class); | |
| final Call<List<Sensor>> call = weatherService.loadData(); | |
| call.enqueue(new Callback<List<Sensor>>() { | |
| @Override | |
| public void onResponse(@NonNull Call<List<Sensor>> call, @NonNull Response<List<Sensor>> response) { | |
| List<Sensor> sensors = response.body(); | |
| if (sensors != null) { | |
| Timber.i("Server response with: %s", sensors); | |
| adapter.updateResults(sensors); | |
| notificationService.checkSensorsState(sensors, mContext); | |
| } | |
| swipeRefreshLayout.setRefreshing(false); | |
| } | |
| @Override | |
| public void onFailure(@NonNull Call<List<Sensor>> call, @NonNull Throwable t) { | |
| swipeRefreshLayout.setRefreshing(false); | |
| Timber.e(t.getLocalizedMessage()); | |
| Toast.makeText(mContext, R.string.connection_error, Toast.LENGTH_LONG).show(); | |
| } | |
| }); | |
| } |
Działanie aplikacji nie zmieniło się wcale, ale kod jest o wiele bardziej czytelny.