- Copy the Do not block main thread! recipe into a new folder.
- Open the project and save it as WeatherForecastsEx.dproj.
- Change the code, as shown in the following steps.
- In the private section of the form declaration, add the following methods:
private Lang: string; procedure AddFooter(AItems: TAppearanceListViewItems; const LMinInTheDay, LMaxInTheDay: Double); procedure AddHeader(AItems: TAppearanceListViewItems; const ADay: String); procedure AddForecastItem(AItems: TAppearanceListViewItems; const AForecastDateTime: TDateTime; const AWeatherDescription: String; const ATempMin, ATempMax: Double);
- Press Ctrl + Shift + C to create the method bodies, and then add the following code:
procedure TMainForm.AddHeader(AItems: TAppearanceListViewItems; const ADay: String); var LItem: TListViewItem; begin LItem := AItems.Add; LItem.Purpose := TListItemPurpose.Header; LItem.Objects.FindDrawable('HeaderLabel').Data := ADay; end; procedure TMainForm.AddForecastItem(AItems: TAppearanceListViewItems; const AForecastDateTime: TDateTime; const AWeatherDescription: String; const ATempMin, ATempMax: Double); var LItem: TListViewItem; begin LItem := AItems.Add; LItem.Objects.FindDrawable('WeatherDescription').Data := FormatDateTime('HH', AForecastDateTime) + ' ' + AWeatherDescription; LItem.Objects.FindDrawable('MinTemp').Data := FormatFloat('#0.00', ATempMin) + '°'; LItem.Objects.FindDrawable('MaxTemp').Data := FormatFloat('#0.00', ATempMax) + '°'; end; procedure TMainForm.AddFooter(AItems: TAppearanceListViewItems; const LMinInTheDay, LMaxInTheDay: Double); var LItem: TListViewItem; begin LItem := AItems.Add; LItem.Purpose := TListItemPurpose.Footer; LItem.Text := Format('min %2.2f°C max %2.2f°C', [LMinInTheDay, LMaxInTheDay]); end;
- Now, we have to use these methods of the form. In the btnGetForecastsClick method, substitute the code with the following:
procedure TMainForm.btnGetForecastsClick(Sender: TObject);
begin
ListView1.Items.Clear;
RESTRequest1.Params.ParameterByName('country').Value :=
String.Join(',', [EditCity.Text, EditCountry.Text]);
RESTRequest1.Params.ParameterByName('lang').Value := Lang;
AniIndicator1.Visible := True;
AniIndicator1.Enabled := True;
btnGetForecasts.Enabled := False;
RESTRequest1.ExecuteAsync(
procedure
var
LForecastDateTime: TDateTime;
LJValue: TJSONValue;
LJObj, LMainForecast, LForecastItem, LJObjCity: TJSONObject;
LJArrWeather, LJArrForecasts: TJSONArray;
LTempMin, LTempMax: Double;
LDay, LLastDay: string;
LWeatherDescription: string;
LAppRespCode: string;
LMinInTheDay: Double;
LMaxInTheDay: Double;
begin
try
LJObj := RESTRequest1.Response.JSONValue as TJSONObject;
// check for errors
LAppRespCode := LJObj.GetValue('cod').Value;
if LAppRespCode.Equals('404') then
begin
lblInfo.Text := 'City not found';
Exit;
end;
if not LAppRespCode.Equals('200') then
begin
lblInfo.Text := 'Error ' + LAppRespCode;
Exit;
end;
// parsing forecasts
LMinInTheDay := 1000;
LMaxInTheDay := -LMinInTheDay;
LJArrForecasts := LJObj.GetValue('list') as TJSONArray;
for LJValue in LJArrForecasts do
begin
LForecastItem := LJValue as TJSONObject;
LForecastDateTime := UnixToDateTime((LForecastItem.GetValue('dt')
as TJSONNumber).AsInt64);
LMainForecast := LForecastItem.GetValue('main') as TJSONObject;
LTempMin := (LMainForecast.GetValue('temp_min')
as TJSONNumber).AsDouble;
LTempMax := (LMainForecast.GetValue('temp_max')
as TJSONNumber).AsDouble;
LJArrWeather := LForecastItem.GetValue('weather') as TJSONArray;
LWeatherDescription := TJSONObject(LJArrWeather.Items[0])
.GetValue('description').Value;
LDay := FormatDateTime('ddd d mmm yyyy', DateOf(LForecastDateTime));
if LDay <> LLastDay then
begin
if not LLastDay.IsEmpty then
begin
AddFooter(ListView1.Items, LMinInTheDay, LMaxInTheDay);
end;
AddHeader(ListView1.Items, LDay);
LMinInTheDay := 1000;
LMaxInTheDay := -LMinInTheDay;
end;
LLastDay := LDay;
LMinInTheDay := Min(LMinInTheDay, LTempMin);
LMaxInTheDay := Max(LMaxInTheDay, LTempMax);
AddForecastItem(ListView1.Items, LForecastDateTime,
LWeatherDescription, LTempMin, LTempMax);
end; // for in
if not LLastDay.IsEmpty then
AddFooter(ListView1.Items, LMinInTheDay, LMaxInTheDay);
LJObjCity := LJObj.GetValue('city') as TJSONObject;
lblInfo.Text := LJObjCity.GetValue('name').Value + ', ' +
LJObjCity.GetValue('country').Value;
finally
AniIndicator1.Visible := False;
AniIndicator1.Enabled := False;
btnGetForecasts.Enabled := True;
end;
end);
end;
- The main difference between the Using SQLite databases to handle a to-do list recipe and this recipe is the complete flexibility of data visualization. To get this flexibility, we added individual controls to each list item. We defined all the needed properties, width, alignment, colors, and so on. When the device switches to landscape orientation, some alignments need to be changed according to the larger horizontal space available. For this situation, a very handy list box UpdateObjects event handler is available. Create an UpdateObjects event handler on the list box and add the following code:
procedure TMainForm.ListView1UpdateObjects( const Sender: TObject; const AItem: TListViewItem); var AQuarter: Double; lb: TListItemText; begin case AItem.Purpose of TListItemPurpose.None: begin AQuarter := (AItem.Parent.Width - TListView(AItem.Parent).ItemSpaces.Left - TListView(AItem.Parent).ItemSpaces.Right) / 4; // AItem.Objects.Clear; AItem.Height := 24; lb := TListItemText.Create(AItem); lb.PlaceOffset.X := 0; lb.TextAlign := TTextAlign.Leading; lb.Name := 'WeatherDescription'; lb := TListItemText.Create(AItem); lb.TextAlign := TTextAlign.Trailing; lb.TextColor := TAlphaColorRec.Blue; lb.Name := 'MinTemp'; lb.PlaceOffset.X := AQuarter * 2; lb.Width := AQuarter; lb := TListItemText.Create(AItem); lb.TextAlign := TTextAlign.Trailing; lb.TextColor := TAlphaColorRec.Red; lb.Name := 'MaxTemp'; lb.PlaceOffset.X := AQuarter * 3; lb.Width := AQuarter; end; TListItemPurpose.Header: begin AItem.Height := 48; lb := TListItemText.Create(AItem); lb.TextAlign := TTextAlign.Center; lb.Align := TListItemAlign.Center; lb.TextColor := TAlphaColorRec.Red; lb.PlaceOffset.Y := AItem.Height / 4; lb.Name := 'HeaderLabel'; end; TListItemPurpose.Footer: begin AItem.Objects.TextObject .TextAlign := TTextAlign.Trailing; end; end;
- With this adjustment, text inside the list item is always aligned correctly.
- Run the app. For testing purposes, you can run the app using the 32-bit Windows target. Here's the app running on an Android phone:
Figure 8.9: The Weather forecasts ex app running in portrait and landscape modes on an Italian Android phone; note how the temperature columns are realigned between the two orientations