Xamarin 양식 응용 프로그램을 구축하여 SpaceX GraphQL API 탐색

이게 어떻게 된 일입니까


우리는 Xamarin을 건설해야 한다.SpaceX GraphQL API에 연결된 폼 응용 프로그램으로 멋진 로켓 발사 사진을 보여 줍니다.유형 보안 인코딩을 위한 C# 클래스 생성, GraphQLHttpClient를 사용한 쿼리 전송, XAML에서 우아한 UI 구축 및 Xamarin 사용 방법을 확인할 수 있습니다.형성 행위.

2
1
🚀

GraphQl 모드에서 유형 생성


여기는 모드를 얻을 수 있는 단점https://api.spacex.land/graphql
만약 당신이 스스로 유형을 만드는 방법이 있다면, 당신은 이 절을 뛰어넘을 수 있지만, 나는 이렇게 한다.
선택한 폴더에서 새 실 프로젝트를 초기화합니다
yarn init
다음은 이러한 개발 의존항을 설치합니다
yarn add -D graphql @graphql-codegen/c-sharp @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript
다음 스크립트 속성을 package.json에 추가
  "scripts": {
    "generate": "graphql-codegen --config codegen.yml"
  }
지금 우리는 이미 소포를 완성했다.json, 우리는 생성 클래스를 담당하는 노드 모듈을 계속 설정할 수 있습니다.
다음은 YAML 파일의 모양입니다.
overwrite: true
schema: "https://api.spacex.land/graphql"
#documents: "src/**/*.graphql"
generates:
  gen/Models.cs:
    plugins:
      - "c-sharp"
    config: 
      namespaceName: SpaceXGraphQL.SpaceX.gen
      scalars:
        timestamptz: DateTime
        uuid: Guid
  ./graphql.schema.json:
    plugins:
      - "introspection"
번역해 보겠습니다: https://api.spacex.land/graphql에서 모드를 가져오고 "c-sharp"플러그인을 사용하여 이름 공간이 SpaceXGraphQL.SpaceX.gen인 클래스를 파일 (self에 비해)gen/Models.cs에 생성합니다.scalars 속성은 GraphQL 모드의 사용자 정의 유형과 일치하는 C# 유형 사이의 매핑입니다.보아하니 그들은 엔진 덮개 아래에서 Postgres를 사용하는 것 같다.timestamptz 시간대를 포함한 날짜와 시간 정보를 저장하고 uuid는 실체를 표시하는 128자리 수량에 불과하다.
GraphiQL(https://api.spacex.land/graphql/)


현재 인용 의존항을 설치합니다
yarn install
그런 다음 스크립트를 실행합니다.
yarn generate
우리 수업이 생겼어!예!🎉

이렇게!우리는 이제 합작할 수 있는 유형이 생겼다.

Xamarin을 적으십시오.양식 적용


새로운 Xamarin 을 생성합니다.Forms app 및 다음 NuGet 패키지 추가
<PackageReference Include="GraphQL.Client" Version="3.2.1" />
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="3.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="3.2.2" />
다음에 생성된 클래스를 공유 프로젝트에 추가합니다.나는 실제로 모든 사선 프로젝트 폴더를 추가했다

이제 재밌게 놀아요!
이런 글로벌 스타일을 세우다
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SpaceXGraphQL.App">
    <Application.Resources>

        <!-- Styles -->
        <Style TargetType="NavigationPage">
            <Setter Property="BarBackgroundColor" Value="#384355"/>
            <Setter Property="BarTextColor" Value="#FF543D"/>
        </Style>
        <!-- Styles -->

    </Application.Resources>
</Application>
응용 프로그램에는 두 개의 페이지가 있습니다.
첫 페이지는 로켓 발사 목록을 표시하고 무한 스크롤을 사용하며 곧 발사될 펄스 지시기를 표시한다.
간단하게 보기 위해서 저는 제3자 UI 라이브러리를 사용하지 않았고 Prism이나 Mvvmcross를 통합하지 않았습니다. 비록 저는 그것들을 매우 좋아하지만.
보기 모형부터 마인LaunchesPageViewModel이라고 명명했습니다.그것은 이렇게 실현되었다INotifyPropertyChanged
 public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
그것은 로켓 발사를 저장하기 위해 몇 가지 속성을 필요로 하며, 데이터를 얻고 있음을 나타내고, 다음/페이지의 발사를 불러오는 명령이 필요하다
        public Command LoadMoreCommand
        {
            get => _loadMoreCommand;
            set
            {
                _loadMoreCommand = value;
                OnPropertyChanged();
            }
        }

        public bool IsLoading
        {
            get => _isLoading;
            set
            {
                _isLoading = value;
                OnPropertyChanged();
            }
        }

        public ObservableCollection<Types.Launch> Launches
        {
            get => _launches;
            set
            {
                _launches = value;
                OnPropertyChanged();
            }
        }
다음에 구조 함수에서 나는 약간의 초기화 작업을 진행할 것이다.LoadMoreCommand 로켓 발사를 획득하고 전번 획득이 완료되지 않았을 때 다시 집행하면 되돌아온다.내가 init를 완성할 때, 나는 잡기 시작했다.
 public LaunchesPageViewModel()
        {
            _client = new GraphQLHttpClient("https://api.spacex.land/graphql", new NewtonsoftJsonSerializer());
            Launches = new ObservableCollection<Types.Launch>();

            LoadMoreCommand = new Command(async () =>
            {
                if (_isFetchingMore)
                {
                    return;
                }

                try
                {
                    _isFetchingMore = true;
                    await GetLaunches();
                }
                finally
                {
                    _isFetchingMore = false;
                }
            });

            IsLoading = true;
            GetLaunches().ContinueWith((_, __) => IsLoading = false, null);
        }
이제 마지막 부분, 실제 GraphQL 조회를 살펴보겠습니다."Launchs"검색을 사용하고 있습니다. 이것은 use가 얼마나 많은 시작 이벤트를 되돌려주고, 얼마나 많은 이벤트를 건너뛰는지 알려주며, 페이지api를 효과적으로 제공합니다.
제가 현재 가지고 있는 발사 수량을 보상으로 사용하고 있으니 주의하세요.
        private async Task GetLaunches()
        {
            var launchesRequest = new GraphQLRequest
            {
                Query = @"query getLaunches($limit: Int, $offset: Int)
                                {
                                  launches(limit: $limit, offset: $offset) {
                                    id
                                    is_tentative
                                    upcoming                                    
                                    mission_name
                                    links {
                                      article_link
                                      video_link
                                      flickr_images
                                      mission_patch
                                    }
                                    launch_date_utc
                                    details
                                  }
                                }",
                Variables = new
                {
                    limit = 15,
                    offset = Launches.Count
                }
            };

            var response = await _client.SendQueryAsync<Types.Query>(launchesRequest);
            if (!(response.Errors ?? Array.Empty<GraphQLError>()).Any())
            {
                foreach (var launch in response.Data.launches)
                {
                    Launches.Add(launch);
                }
            }
        }
이것이 LaunchesPageViewModel의 전부다.LaunchesPage는 XAML에서만 가능하며 프로세서 선택을 제외합니다.
기본적으로 나는 CollectionView(가상화에 사용)를 사용하여 발표를 보여주고 있다.주의 속성RemainingItemsThresholdRemainingItemsThresholdReachedCommand.5개나 더 적은 항목이 표시될 때마다 CollectionView 호출됩니다. LoadMoreCommand 다음의 시작을 위해 GraphQL API에 검색어를 보냅니다.
발사하다.xaml
<!-- Loading -->
        <ActivityIndicator IsVisible="{Binding IsLoading}"
                           IsRunning="True"
                           Color="#FF543D"
                           VerticalOptions="Center" HorizontalOptions="Center"
                           WidthRequest="120" HeightRequest="120" />

        <!-- Launches -->
        <CollectionView ItemsSource="{Binding Launches}"
                        ItemSizingStrategy="MeasureFirstItem"
                        SelectionMode="Single"
                        SelectionChanged="OnLaunchSelected"
                        RemainingItemsThreshold="5"
                        RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
                        Margin="10">
            <CollectionView.ItemsLayout>
                <LinearItemsLayout Orientation="Vertical"
                                   ItemSpacing="5.0" />
            </CollectionView.ItemsLayout>

            <!-- Launch Template -->
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Frame CornerRadius="15"
                           Padding="0" Margin="5,0,5,0"
                           HasShadow="False"
                           BackgroundColor="#041727"
                           IsClippedToBounds="True">
                        <Grid RowDefinitions="*,*"
                              ColumnDefinitions="Auto,*"
                              HeightRequest="80">

                            <!-- Mission patch -->
                            <Image HeightRequest="60" WidthRequest="60"
                                   Margin="10"
                                   Grid.Row="0" Grid.Column="0" Grid.RowSpan="2"
                                   Aspect="AspectFit"
                                   HorizontalOptions="CenterAndExpand"
                                   VerticalOptions="CenterAndExpand"
                                   Source="{Binding links.mission_patch}" />
                            <Label Grid.Row="0" Grid.Column="1"
                                   VerticalOptions="End"
                                   TextColor="White"
                                   FontAttributes="Bold"
                                   Text="{Binding mission_name}" />
                            <Label Grid.Row="1" Grid.Column="1"
                                   FontSize="Small"
                                   TextColor="#FF543D"
                                   VerticalOptions="Start"
                                   Text="{Binding launch_date_utc}" />

                            <!-- Upcoming indicator -->
                            <BoxView Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
                                     HeightRequest="10" WidthRequest="10"
                                     Margin="20"
                                     CornerRadius="5"
                                     HorizontalOptions="End" VerticalOptions="Center"
                                     Color="#FF543D"
                                     IsVisible="False">
                                <BoxView.Behaviors>
                                    <behaviors:PulseBehavior />
                                </BoxView.Behaviors>
                                <BoxView.Triggers>
                                    <DataTrigger TargetType="BoxView"
                                                 Binding="{Binding Path=upcoming}"
                                                 Value="True">
                                        <DataTrigger.Setters>
                                            <Setter Property="IsVisible" Value="True" />
                                        </DataTrigger.Setters>
                                    </DataTrigger>
                                </BoxView.Triggers>
                            </BoxView>
                        </Grid>
                    </Frame>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
발사하다.xaml.대테러 엘리트
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class LaunchesPage : ContentPage
    {
        public LaunchesPage()
        {
            InitializeComponent();
            BindingContext = new LaunchesPageViewModel();
        }

        private async void OnLaunchSelected(object sender, SelectionChangedEventArgs e)
        {
            if (e.CurrentSelection.Any() && e.CurrentSelection.First() is Types.Launch launch)
            {
                await Navigation.PushAsync(new LaunchPage(new LaunchPageViewModel(launch.id)));

                if (sender is CollectionView cw)
                {
                    cw.SelectedItem = null;
                }
            }
        }
    }
펄스 행위.대테러 엘리트
using Xamarin.Forms;
using Xamarin.Forms.Internals;

namespace SpaceXGraphQL.Behaviors
{
    public class PulseBehavior : Behavior<View>
    {
        private const string PulsingAnimation = "Pulsing";
        private bool _running = false;
        private bool _isReversed = false;

        protected override void OnAttachedTo(View bindable)
        {
            base.OnAttachedTo(bindable);

            _running = true;
            bindable.Animate(PulsingAnimation,
                d =>
                {
                    var newScale = _isReversed ? 1.0d - d : d;
                    bindable.Scale = newScale.Clamp(0.3, 1.0);
                },
                length: 1000,
                easing: Easing.CubicInOut,
                repeat: () =>
                {
                    _isReversed = !_isReversed;
                    return _running;
                });
        }

        protected override void OnDetachingFrom(View bindable)
        {
            base.OnDetachingFrom(bindable);

            _running = false;
            bindable.AbortAnimation(PulsingAnimation);
        }
    }
}
우리는 첫 페이지를 완성했는데, 결과는 다음과 같다.

이제 두 번째 페이지로 들어갑니다.가장 멋진 게시 사진, 게시 이름, 패치, 원본 글의 링크, 사진 공유 등 발표 세부 사항을 보여 줍니다.
우리는 내가 명명한 보기 모형LaunchPageViewModel부터 시작한다.그것은 완전히 같은 INotifyPropertyChanged로 이루어졌기 때문에 나는 여기서 생략했다.
이 보기 모델은 하나의 발표 데이터를 저장하기 위해 속성을 필요로 하고, 원시 글 링크를 열고 발표 이미지를 공유하기 위해 명령을 필요로 합니다.
        public Types.Launch Launch
        {
            get => _launch;
            set
            {
                _launch = value;
                OnPropertyChanged();
            }
        }

        public Command ArticleLinkTappedCommand { get; set; }
        public Command ImageTappedCommand { get; set; }
구조 함수에서 보기 모형은 구성원과 두 개의 명령을 초기화하고 Xamarin Essentials를 사용하여 링크를 열고 데이터를 공유합니다.
구성 함수는 시작 id를 매개 변수로 합니다. 이 함수는 데이터를 조회하고 표시하는 시작 id입니다.
마지막으로 GetLaunch() 메서드의 모양새는 다음과 같습니다.
private async Task GetLaunch()
        {
            Launch = null;
            var launchRequest = new GraphQLRequest
            {
                Query = @"query getLaunch($id: ID!) {
                                      launch(id: $id) {
                                        id
                                        is_tentative
                                        upcoming
                                        mission_name
                                        links {
                                          article_link
                                          video_link
                                          flickr_images
                                          mission_patch
                                        }
                                        launch_date_utc
                                        details
                                      }
                                    }",
                Variables = new
                {
                    id = _launchId,
                }
            };

            var response = await _client.SendQueryAsync<Types.Query>(launchRequest);
            if (!(response.Errors ?? Array.Empty<GraphQLError>()).Any())
            {
                Launch = response.Data.launch;
            }
        }
마찬가지로 이 페이지는 거의 완전히 XAML로 이루어졌다.
시작 페이지.xaml
<?xml version="1.0" encoding="utf-8"?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:converters="clr-namespace:SpaceXGraphQL.Converters;assembly=SpaceXGraphQL"
             x:Class="SpaceXGraphQL.LaunchPage"
             BackgroundColor="#041727"
             x:Name="ParentPage">

    <ContentPage.Resources>
        <converters:GetLastItemConverter x:Key="GetLastItemConverter" />
    </ContentPage.Resources>

    <ScrollView Padding="20, 0">
        <StackLayout>
            <!-- Top image -->
            <Grid VerticalOptions="Start">
                <Frame CornerRadius="20"
                       Padding="0"
                       HasShadow="True"
                       IsClippedToBounds="True">
                    <Image Source="{Binding Launch.links.flickr_images, Converter={StaticResource GetLastItemConverter}}"
                           HorizontalOptions="FillAndExpand"
                           BackgroundColor="#384355"
                           Aspect="AspectFill"
                           HeightRequest="250" />
                </Frame>

                <!-- Patch, name and date -->
                <StackLayout Orientation="Horizontal"
                             HorizontalOptions="Start" VerticalOptions="Start"
                             Margin="20">
                    <Image Source="{Binding Launch.links.mission_patch}"
                           Aspect="AspectFit"
                           HeightRequest="40" WidthRequest="40" />
                    <StackLayout>

                        <Label Text="{Binding Launch.mission_name}"
                               TextColor="White"
                               FontAttributes="Bold" FontSize="Large"
                               HorizontalOptions="Start" VerticalOptions="Center" />
                        <Label Text="{Binding Launch.launch_date_utc}"
                               TextColor="LightGray"
                               FontAttributes="Bold" FontSize="Micro"
                               HorizontalOptions="Start" VerticalOptions="Center" />
                    </StackLayout>
                </StackLayout>
            </Grid>

            <!-- Article link -->
            <Label VerticalOptions="End" HorizontalOptions="End"
                   Margin="0, 5, 20,0">
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="Article Link" TextColor="RoyalBlue"
                              FontSize="Small" FontAttributes="Bold">
                            <Span.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding ArticleLinkTappedCommand}" />
                            </Span.GestureRecognizers>
                        </Span>
                    </FormattedString>
                </Label.FormattedText>
            </Label>

            <!-- Description -->
            <Label Text="Description" TextColor="#FF543D" FontSize="Large"
                   FontAttributes="Bold"
                   Margin="0, 20,0,5" />

            <Label Text="{Binding Launch.details}"
                   FontSize="Body"
                   TextColor="White" />

            <!-- Media -->
            <Label Text="Media" TextColor="#FF543D"
                   FontSize="Large" FontAttributes="Bold" Margin="0, 20,0,5" />
            <StackLayout BindableLayout.ItemsSource="{Binding Launch.links.flickr_images}">
                <BindableLayout.EmptyView>
                    <Label Text="Loading ..." TextColor="White" FontSize="Small"
                           HorizontalOptions="Center"/>
                </BindableLayout.EmptyView>

                <!-- Images -->
                <BindableLayout.ItemTemplate>
                    <DataTemplate>
                        <SwipeView>
                            <SwipeView.RightItems>
                                <SwipeItems>
                                    <SwipeItem Text="Share"
                                               BackgroundColor="#FF543D"
                                               Command="{Binding Source={x:Reference ParentPage}, Path=BindingContext.ImageTappedCommand}"
                                               CommandParameter="{Binding .}" />
                                </SwipeItems>
                            </SwipeView.RightItems>
                            <Frame CornerRadius="20"
                                   Padding="0" Margin="0,0,0,5"
                                   HasShadow="True"
                                   IsClippedToBounds="True">
                                <Image Source="{Binding .}"
                                       HorizontalOptions="FillAndExpand"
                                       BackgroundColor="#384355"
                                       Aspect="AspectFill"
                                       HeightRequest="250" />
                            </Frame>
                        </SwipeView>

                    </DataTemplate>
                </BindableLayout.ItemTemplate>
            </StackLayout>
        </StackLayout>
    </ScrollView>
</ContentPage>
시작 페이지.xaml.대테러 엘리트
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace SpaceXGraphQL
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class LaunchPage : ContentPage
    {

        public LaunchPage(LaunchPageViewModel viewModel)
        {
            InitializeComponent();
            BindingContext = viewModel;
        }
    }
}
GetLastItemConverter.대테러 엘리트
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Xamarin.Forms;

namespace SpaceXGraphQL.Converters
{
    public class GetLastItemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is IEnumerable<object> enumerable)
            {
                return new List<object>(enumerable).Last();
            }

            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
우리는 두 번째 페이지와 마지막 페이지를 완성했는데 결과는 아래와 같다

결론


유형을 생성할 때 발생하는 문제 외에 GraphQL API에 연결하는 것은 매우 간단합니다.데이터를 무료로 제공해 주셔서 감사합니다!
스페이스X의 GraphQL 모드를 처리할 수 있는 다른 코드 생성 도구를 사용하고 있다면 알려 주십시오.
나는 두 오후에 이 응용을 실현하는 것이 매우 재미있다고 말할 수 밖에 없다.나는 Xamarin의 뛰어난 활약에 놀랐다.폼 열을 다시 불러오는 것은 이미 하나의 추세가 되었다.내가 지난번에 검사한 것은 2020년 여름이었는데, 그것의 안정성이 훨씬 떨어졌다.이번에 나는 그것으로 나의 응용 프로그램의 원형을 만들 수 있다.
만약 네가 이 점을 할 수 있다면, 나는 너에게 보너스를 줄 것이다!내 GitHub 재구매 계약에 대한 링크입니다.https://github.com/mariusmuntean/SpaceXGraphQL

좋은 웹페이지 즐겨찾기