paint-brush
Flutter için Sunucu Odaklı UI Motoru Nasıl Oluşturulurile@alphamikle
1,228 okumalar
1,228 okumalar

Flutter için Sunucu Odaklı UI Motoru Nasıl Oluşturulur

ile Mike Alfa30m2024/06/13
Read on Terminal Reader

Çok uzun; Okumak

Daha büyük bir proje olan Backendless CMS Nanc'ın parçası olan Flutter için Nui - Sunucu Odaklı UI motorunun yaratılışının arkasındaki hikaye.
featured image - Flutter için Sunucu Odaklı UI Motoru Nasıl Oluşturulur
Mike Alfa HackerNoon profile picture
0-item
1-item

Merhaba!

Bugün size, süper kandırılan bir CMS'nin ayrılmaz bir parçası olan Flutter'da Sunucu Odaklı Kullanıcı Arayüzü için süper kandırılan bir motorun nasıl oluşturulacağını göstereceğim (bunun yaratıcısı, yani ben onu bu şekilde konumlandırıyorum). Elbette farklı bir fikriniz olabilir ve bunu yorumlarda tartışmaktan memnuniyet duyarım.


Bu makale serideki iki (zaten üç) makalenin ilkidir. Bu yazıda doğrudan Nui'ye bakacağız ve bir sonraki makalemizde Nui'nin Nanc CMS ile ne kadar derin bir şekilde entegre olduğuna bakacağız ve bununla bir sonraki makale arasında Nui performansı hakkında büyük miktarda bilgi içeren başka bir makale olacak.


Bu makalede Sunucu Odaklı Kullanıcı Arayüzü, Nui (Nanc Sunucu Odaklı Kullanıcı Arayüzü) yetenekleri, proje geçmişi, bencil ilgi alanları ve Doctor Strange hakkında birçok ilginç şey olacak. Ah evet, GitHub ve pub.dev'e bağlantılar da olacak, yani eğer beğenirseniz ve 1-2 dakikanızı ayırmanın bir sakıncası yoksa, yıldızınızı alıp beğenmekten memnuniyet duyarım.


İçindekiler

  1. Giriş
  2. Gelişimin Nedenleri
  3. Kavramın ispatı
  4. Sözdizimi
  5. IDE Düzenleyici
  6. Verim
  7. Bileşenler ve Kullanıcı Arayüzü Oluşturma
  8. Oyun alanı
  9. Etkileşim ve Mantık
  10. Veri aktarımı
  11. Dokümantasyon
  12. Gelecek planları

Küçük Bir Giriş

Nanc hakkında zaten bir makale yazmıştım, ancak o zamandan bu yana bir yıldan fazla zaman geçti ve proje yetenekler ve "tamlık" açısından önemli ölçüde ilerleme kaydetti ve en önemlisi, bitmiş belgelerle ve MIT altında yayınlandı. lisans.

Peki Nanc nedir?

Arka ucunu kendisiyle birlikte sürüklemeyen genel amaçlı bir CMS'dir. Aynı zamanda React Admin gibi bir şey de değil, nerede bir şeyler yaratılır, tonlarca kod yazmanız gerekir.


Nanc'ı kullanmaya başlamak için şunları yapmanız yeterlidir:

  1. Dart DSL kullanarak CMS aracılığıyla yönetmek istediğiniz veri yapılarını açıklayın
  2. CMS ile arka ucunuz arasındaki iletişimi uygulayan bir API katmanı yazın


Üstelik ilki tamamen CMS'nin arayüzü aracılığıyla yapılabilir - yani veri yapılarını kullanıcı arayüzü aracılığıyla yönetebilirsiniz. İkincisi şu durumlarda atlanabilir:

  1. Firebase kullanıyorsunuz
  2. Veya Supabase kullanıyorsunuz
  3. Veya Nanc'ı gerçek bir arka uca bağlamadan yerel bir veritabanıyla oynamak ve çalıştırmak istiyorsunuz (şimdilik bu rol bir JSON dosyası veya LocalStorage tarafından oynanıyor)


Böylece bazı senaryolarda herhangi bir içeriğinizi ve verilerinizi yönetmek için bir CMS almak için tek satır kod yazmanıza gerek kalmayacaktır. Gelecekte bu senaryoların sayısı artacak; buna ek olarak GraphQL ve RestAPI da artacak. Bir SDK'yı başka ne için uygulamanın mümkün olabileceğine dair fikirleriniz varsa, yorumlardaki önerileri okumaktan memnuniyet duyarım.


Nanc, veri depolama katmanı düzeyinde bir tablo (SQL) veya bir belge (No-SQL) olarak temsil edilebilen varlıklarla, yani modellerle çalışır. Her varlığın alanları vardır - SQL'deki sütunların temsili veya No-SQL'deki aynı "alanlar".


Olası alan türlerinden biri "Ekran" türü olarak adlandırılan türdür. Yani bu makalenin tamamı CMS'deki yalnızca bir alanın metnidir. Aynı zamanda, mimari olarak da şuna benziyor - Nui adı verilen Sunucu Odaklı UI Motorunu birlikte uygulayan tamamen ayrı bir kütüphane ( aslında birkaç kütüphane ) var. Bu işlevsellik CMS'ye entegre edilmiştir ve bunun üzerine birçok ek özellik eklenmiştir.


Bununla doğrudan Nanc'a ayrılan giriş bölümünü bitiriyorum ve Nui hakkındaki hikayeye başlıyorum.

Hepsi nasıl başladı

Yasal Uyarı: Tüm tesadüfler tesadüfidir. Bu hikaye kurgusaldır. Hayal ettim.

Büyük bir şirkette aynı anda birkaç uygulama üzerinde çalıştım. Büyük ölçüde benzerlerdi ama aynı zamanda birçok farklılıkları da vardı.

Ama içlerinde tamamen aynı olan şey, makale motoru diyebileceğim şeydi. Arka uçtan JSON'u işleyen birkaç (5-10-15, artık tam olarak hatırlamıyorum) binlerce satırlık buruşuk koddan oluşuyordu. Bu JSON'lar sonunda kullanıcı arayüzüne, daha doğrusu mobil uygulamada okunacak bir makaleye dönüşmek zorunda kaldı.


Makaleler yönetici paneli kullanılarak oluşturulup düzenlendi ve yeni öğelerin eklenmesi süreci çok, inanılmaz, son derece sancılı ve uzundu. Bu dehşeti görünce, ilk optimizasyonu önermeye karar verdim - zayıf içerik yöneticilerine merhamet etmek ve onlar için makaleleri doğrudan tarayıcıda, yönetici panelinde gerçek zamanlı olarak önizleme işlevini uygulamak.


Söylendi ve yapıldı. Bir süre sonra, uygulamanın sıska bir parçası yönetici panelinde dönmeye başladı ve içerik yöneticilerine değişiklikleri önizlerken çok fazla zaman kazandırdı. Daha önce derin bir bağlantı oluşturmaları ve ardından her değişiklik için geliştirici yapısını açmaları, bu bağlantıyı takip etmeleri, indirmeleri beklemeleri ve ardından her şeyi tekrarlamaları gerekiyorsa, artık makaleler oluşturup bunları hemen görebilirler.


Ancak düşüncem burada bitmedi - bu motordan ve diğer geliştiricilerden çok rahatsız oldum çünkü ona bir şey eklemeleri mi yoksa sadece Augean ahırlarını temizlemeleri mi gerektiğini belirlemek mümkündü.


İkincisi olsaydı, geliştiricinin toplantılarda her zaman iyi bir ruh hali içinde olması gerekirdi; ancak koku... kamera bunu yakalayamıyor.

Birincisi olsaydı, geliştirici sıklıkla hastaydı, depremler yaşamıştı, bozulmuş bir bilgisayar, baş ağrısı, göktaşı çarpması, son aşamada depresyon ya da aşırı dozda ilgisizlik yaşıyordu.


Motorun işlevselliğini genişletmek, içerik yöneticilerinin yeni özelliklerden yararlanabilmesi için yönetici paneline çok sayıda yeni alanın eklenmesini de gerektirdi.


Bütün bunlara baktığımda inanılmaz bir düşünce aklıma geldi: neden bu soruna genel bir çözüm yaratmayalım? Her yeni öğe için yönetici panelini ve uygulamayı sürekli olarak ayarlamamızı ve genişletmemizi engelleyecek bir çözüm. Sorunu kesin olarak çözecek bir çözüm! Ve işte geliyor...

Sinsi açgözlü küçük plan

Şöyle düşündüm: "Bu sorunu çözebilirim. Şirketi yüzbinlerce olmasa da onlarca kurtarabilirim; ancak bu fikir, şirket için onu hediye olarak veremeyecek kadar değerli olabilir."


Hediye derken, şirket için potansiyel değer oranının, şirketin bana maaş şeklinde ödeyeceği miktardan büyüklük sırasına göre farklı olduğunu kastediyorum. Bu, erken bir aşamada, büyük bir şirkette size teklif edilenden daha düşük bir maaşla ve şirkette payınız olmadan bir startup'ta çalışmaya başlamanız gibidir. Ve sonra girişim bir tek boynuzlu at haline gelir ve size şöyle derler: "Dostum, sana maaş ödedik." Ve haklı olacaklar!


Analojileri severim ama bana sık sık bunların benim güçlü yanım olmadığı söylenir. Sanki okyanusta yüzmeyi seven bir balıksınız ama tatlı su balığısınız.


Ve sonra boş zamanlarımda, uygulanması bile mümkün olmayabilecek bazı fikirler sunarak işleri batırmamak için bir kavram kanıtı (POC) yapmaya karar verdim.

Kavramın ispatı

Orijinal plan, işaretlemeyi oluşturmak için mevcut bir hazır kitaplığı kullanmak, ancak işaretleme listesindeki standart öğelerin yanı sıra çok daha karmaşık bir şeyi de oluşturabilecek şekilde yeteneklerini genişletmekti. Makaleler sadece resimli metinlerden ibaret değildi. Ayrıca güzel bir görsel tasarım, yerleşik ses oynatıcıları ve çok daha fazlası vardı.


Bu hipotezi test etmek için Cuma akşamından Pazartesi sabahına kadar 40 saat harcadım - bu kütüphane yeni özellikler için ne kadar genişletilebilir, genel olarak her şey ne kadar iyi çalışıyor ve en önemlisi - bu çözümün kötü şöhretli motoru tahttan devirip deviremeyeceği. Hipotez doğrulandı - kütüphaneyi kemiklerine ayırdıktan ve biraz yama yaptıktan sonra, herhangi bir kullanıcı arayüzü öğesini anahtar kelimelere veya özel sözdizimi yapılarına göre kaydetmek mümkün hale geldi, tüm bunlar kolayca genişletilebilir ve en önemlisi - gerçekten makale motorunun yerini alabilir . 15 saatte bir yere geldim. Kalan 25 parayı POC'yi sonuçlandırmak için harcadım.


Fikir yalnızca bir motoru diğeriyle değiştirmek değildi; hayır. Fikir tüm süreci değiştirmekti! Yönetici paneli yalnızca makale oluşturmanıza olanak sağlamakla kalmaz, aynı zamanda uygulamada görünen içerikleri de yönetir. Orijinal fikir, belirli bir projeye bağlı olmayan ancak projenin yönetilmesine olanak tanıyan eksiksiz bir yedek parça oluşturmaktı. En önemlisi, bu değiştirme aynı zamanda bu makaleler için kullanışlı bir düzenleyici sağlamalıdır, böylece bunların oluşturulabilmesi ve sonucun hemen görülebilmesi sağlanır.


POC için sadece editör yapmanın yeterli olacağını düşündüm. Şunun gibi bir şeye benziyordu:

Kullanıcı Arayüzü Düzenleyicisi

40 saat sonra, işaretleme ve bir grup özel XML etiketinin (örneğin, <container> ) çalkantılı bir karışımından oluşan, çalışan bir kod düzenleyicim, bu koddaki kullanıcı arayüzünü gerçek zamanlı olarak görüntüleyen bir önizlemem ve aynı zamanda en büyüğüne sahip oldum. Bu dünyanın şimdiye kadar gördüğü göz torbaları. Ayrıca, kullanılan "kod düzenleyicinin" sözdizimi vurgulama yeteneğine sahip başka bir kitaplık olduğunu da belirtmek gerekir, ancak sorun şu ki, işaretlemeyi vurgulayabilir, aynı zamanda XML'i de vurgulayabilir, ancak bir hodgepodge'un vurgulanması sürekli bozulur. Yani 40 saat boyunca, bir kimeranın maymun kodlaması için her ikisinin de tek bir şişede vurgulanmasını sağlayacak birkaç tane daha ekleyebilirsiniz. Artık sormanın zamanı geldi; sonra ne oldu?


İlk Demo

Sırada demo vardı. Birkaç üst düzey yöneticiyi bir araya topladım, onlara sorunun çözümüne yönelik vizyonumu, bu vizyonu pratikte doğruladığımı anlattım ve neyin, nasıl çalıştığını, hangi olasılıklara sahip olduğunu gösterdim.


Adamlar işi beğendiler. Ve onu kullanma arzusu vardı. Ama aynı zamanda kemiren bir açgözlülük de vardı. Açgözlülüğüm. Bunu şirkete bu şekilde veremez miyim? Tabii ki değil. Ama ben de planlamadım. Demo, zanaatımla onları şok ettiğim cesur bir planın parçasıydı; direnemediler ve bu inanılmaz, ayrıcalıklı ve muhteşem gelişmeyi kullanmak için her türlü koşulu karşılamaya hazırdılar. Bu kurgusal(!) hikayenin tüm detaylarını açıklamayacağım, sadece para istediğimi söyleyeceğim. Para ve tatil. Bir aylık ücretli tatil ve para. Ne kadar para o kadar önemli değil, sadece miktarın maaşımla ve 6 rakamıyla bağlantılı olması önemli.

Ama ben tamamen pervasız bir gözüpek değildim.


Dormammu, pazarlık yapmaya geldim. Ve anlaşma şuydu: Kendi modumda iki tam hafta çalışıyorum ( 4 saat uyku, 20 saat çalışma ), POC'yi "uygulama amaçlarımız için kullanılabilir" durumuna kadar bitiriyorum ve buna paralel olarak uyguluyorum uygulamada yeni bir özellik - bu ultra şeyi kullanan tam bir ekran (başlangıçta bu iki haftanın tahsis edildiği). Ve iki haftanın sonunda bir demo daha gerçekleştiriyoruz. Ancak bu sefer daha fazla insanı, hatta şirketin üst yönetimini bile bir araya getiriyoruz ve eğer gördükleri şey onları etkilerse ve bunu kullanmak isterlerse, anlaşma yapılır, ben arzularımı alırım ve şirket süper bir silah alır. Eğer bunların hiçbirini istemiyorlarsa, bu iki hafta boyunca bedava çalıştığım gerçeğini kabul etmeye hazırım.


Pedra Furada (Urubici yakınında)

Bir aylık tatilim için planladığım Urubici gezisi ne yazık ki gerçekleşmedi. Yöneticiler böyle bir cüretkarlığı kabul etmeye cesaret edemediler. Ve ben bakışlarımı yere indirerek "klasik şekilde" yeni bir ekranı oymaya gittim. Ancak kadere yenik düşen ana karakterin dizlerinden kalkıp canavarını yeniden evcilleştirmeye çalışmadığı bir hikaye yoktur.


Hayır olmasına rağmen... öyle görünüyor ki: 1 , 2 , 3 , 4 , 5 .


Bütün bu filmleri izledikten sonra bunun bir işaret olduğuna karar verdim! Ve bu şekilde daha da iyi - bu kadar umut verici bir gelişmeyi orada bazı güzellikler için satmak üzücü ( kimi kandırıyorum??? ) ve projemi daha da geliştirmeye devam edeceğim. Ve devam ettim. Ama artık hafta sonları 40 saat değil, haftada sadece 15-20 saat, nispeten sakin bir tempoda.

Kodlamak mı, kodlamamak mı?

4. duvarı kırmak kolay bir iş değil. Tıpkı okuyucunun okumaya devam etmesini ve şirketle ilgili hikayenin nasıl biteceğini beklemesini sağlayacak ilginç başlıklar bulmaya çalışmak gibi. Hikayeyi ikinci yazımda bitireceğim. Ve şimdi öyle görünüyor ki, uygulamaya, işlevsel yeteneklere ve teoride bu makaleyi teknik ve HackerNoon'u daha büyük hale getirecek her şeye geçmenin zamanı geldi!

Sözdizimi

Konuşacağımız ilk şey sözdizimidir. Orijinal hodgepodge fikri POC için uygundu ancak uygulamanın gösterdiği gibi indirim o kadar basit değil. Ayrıca, bazı yerel işaretleme öğelerini yalnızca Flutter öğeleriyle birleştirmek her zaman tutarlı değildir.


İlk soru şudur: resim ![Description](Link) yoksa <image> mi olacak?


İlki ise - bir sürü parametreyi nereye koyacağım?

İkincisi ise - o zaman neden ilkine sahibiz?


İkinci soru ise metinler. Flutter'ın metinleri şekillendirme olanakları sınırsızdır. İndirim olasılıkları "öyle-öyle"dir. Evet, metni kalın veya italik olarak işaretleyebilirsiniz ve hatta bu yapıları ** / __ stillendirme için kullanma düşünceleri bile vardı. Sonra ortaya <color="red"> text </color> etiketlerini sokmak gibi düşünceler geldi ama bu öyle bir eğri ve sürünme ki gözlerden kan akıyor. Kendi marjinal sözdizimine sahip bir tür HTML almak hiç de arzu edilen bir şey değildi. Ayrıca fikir, bu kodun teknik bilgisi olmayan yöneticiler tarafından bile yazılabilmesiydi.


Adım adım kimeranın bir kısmını çıkardım ve bir süper mutanta sahip oldum. Yani, işaretlemeyi oluşturmak için yamalı bir kitaplığımız var, ancak özel etiketlerle doldurulmuş ve işaretleme desteği yok. Yani sanki XML'imiz varmış gibi.


Başka hangi basit söz dizimlerinin mevcut olduğunu düşünmek ve denemek için oturdum. JSON cüruftur. Bir kişiye çarpık bir Flutter editöründe JSON yazmasını sağlamak, sizi öldürmek isteyecek bir manyağın ortaya çıkmasıdır. Ve mesele sadece bununla ilgili değil, bana öyle geliyor ki JSON genel olarak bir kişi tarafından, özellikle de kullanıcı arayüzü için yazmaya uygun değil - sürekli sağa doğru büyüyor, bir sürü zorunlu "" , yorum yok. YAML mı? Pekala belki. Ancak kod aynı zamanda yana doğru da tarayacaktır. İlginç bağlantılar var, ancak yalnızca onların yardımıyla pek bir şey başaramazsınız. TOML'mü? Pf-ff.


Tamam, sonuçta XML'e karar verdim. Bana öyle geldi ve hala öyle görünüyor ki, bu oldukça "yoğun" bir sözdizimi ve kullanıcı arayüzüne çok uygun. Sonuçta, HTML düzen tasarımcıları hala var ve burada her şey webdekinden daha basit olacak ( muhtemelen ).


Daha sonra şu soru ortaya çıktı: bazı vurgulama/kod tamamlama olasılığını elde etmek güzel olurdu. Mantıksal yapıların yanı sıra, bir nevi {{ user.name }} . Daha sonra Twig, Liquid ile deneyler yapmaya başladım, artık hatırlamadığım diğer şablon motorlara baktım. Ancak başka bir sorunla karşılaştım - örneğin Twig gibi standart bir motorda planlananın bir kısmını uygulamak oldukça mümkün, ancak her şeyi uygulamak kesinlikle işe yaramayacak. Ve evet, otomatik tamamlama ve vurgulamanın olması iyi, ancak bunlar yalnızca kendi yeni özelliklerinizi Flutter için gerekli olacak standart Twig sözdiziminin üzerine koyarsanız müdahale edeceklerdir. Sonuç olarak XML ile her şey çok iyi sonuçlandı, Twig / Liquid ile yapılan deneyler olağanüstü sonuçlar vermedi ve hatta bazı noktalarda bazı özellikleri uygulamanın imkansızlığıyla karşılaştım. Bu nedenle seçim hala XML'de kaldı. Özellikler hakkında daha fazla konuşacağız, ancak şimdilik Twig/Liquid'de çok cazip gelen otomatik tamamlama ve vurgulamaya odaklanalım.


IDE

Bundan sonra söylemek istediğim şey Flutter'ın çarpık metin girişlerine sahip olduğudur. Mobil formatta iyi çalışırlar. Masaüstü formatında da iyi bir şey söz konusu olduğunda, maksimum 5-10 satır yüksekliğinde. Ancak konu, bu düzenleyicinin Flutter'da uygulandığı tam teşekküllü bir kod düzenleyiciye gelince, ona gözyaşları olmadan bakamazsınız. Tüm görevleri takip ettiğim, notlar ve fikirler yazdığım Trello'da şöyle bir "görev" var:


Kullanıcı arayüzü kod düzenleyicisini değiştirme görevi


Aslında proje üzerinde çalışmaya başladığımdan beri Nui kod düzenleyicisini daha uygun bir şeyle değiştirme fikrini aklımda tuttum. Diyelim ki - VS Code'un Açık Kaynak kısmıyla bir web görünümü ekleyin. Ancak şu ana kadar elim buna ulaşamadı, ayrıca bu editörün eğriliği sorununa çetrefilli ama hala işe yarayan bir çözüm aklıma geldi: bunun yerine kendi geliştirme ortamınızı kullanmak.


Bu şu şekilde elde edilir - UI kodlu (XML), ideal olarak .html / .twig uzantılı bir dosya oluşturun, aynı dosyayı CMS - Web / Masaüstü / Yerel / Dağıtılmış - aracılığıyla açın - önemli değil. Ve aynı dosyayı herhangi bir IDE aracılığıyla, hatta VS Code'un web sürümü aracılığıyla açın. Ve işte - bu dosyayı favori aracınızda düzenleyebilir ve doğrudan tarayıcıda veya herhangi bir yerde gerçek zamanlı bir önizlemeye sahip olabilirsiniz.


Nanc + IDE Senkronizasyonu


Böyle bir senaryoda, tam teşekküllü otomatik tamamlamayı bile kullanabilirsiniz. VS Code'da bunu özel HTML etiketleri aracılığıyla uygulama olanağı vardır. Ancak VS Code kullanmıyorum, tercihim IntelliJ IDEA ve bu IDE için artık bu kadar basit bir çözüm yok (en azından yoktu veya en azından ben bulamadım). Ancak hem orada hem de orada işe yarayacak daha genel bir çözüm var: XML Şema Tanımı (XSD). Bu canavarı çözmek için yaklaşık 3 akşam harcadım ama başarı gelmedi ve sonunda bu konuyu daha iyi zamanlara bırakarak bıraktım.


İlginçtir ki, birçok deney yinelemesinden, örneğin XML'i widget'lara dönüştürmekten sorumlu motor güncellemelerinden sonra, dilin özellikle önemli olmadığı böyle bir çözüme sahip olduk. Tıpkı kullanıcı arayüzünüzün yapısı hakkında bir bilgi taşıyıcısı olarak, seçim sonunda XML'e düştü, ancak aynı zamanda JSON'u ve hatta ikili bir formu - derlenmiş Protobuf'u güvenli bir şekilde besleyebilirsiniz. Bu da bizi bir sonraki konuya getiriyor.


Verim

Bu cümlede bu yazının boyutu 3218 kelime olacaktır. Bu bölümü yazmaya başladığımda, her şeyi niteliksel olarak yapmak için Nui ve normal Flutter'ın görüntü oluşturma performansını karşılaştıran birçok test senaryosu yazmak gerekiyordu. Zaten tamamen Nui'de oluşturulmuş bir demo ekranım olduğundan:


Namart Ekran Demosu


ekranın tam bir eşleşmesini yerel olarak oluşturmak gerekiyordu (tabii ki Flutter bağlamında). Sonuç olarak, aynı şeyi yeniden yazmak, test sürecini iyileştirmek ve gittikçe daha ilginç sayılar elde etmek 3 haftadan fazla sürdü. Ve bu bölümün boyutu tek başına 3500 kelimeyi aştı. Bu nedenle, tamamen ve tamamen Nui'nin performansına, özel bir durum olarak ve kullanmaya karar verirseniz ödemek zorunda kalacağınız ek ücrete ayrılacak ayrı bir makale yazmanın mantıklı olduğu fikrine vardım. Bir yaklaşım olarak Sunucu Odaklı Kullanıcı Arayüzü.


Ancak küçük bir spoiler vereceğim: Performansı değerlendirmek için dikkate aldığım iki ana senaryo vardı: ilk oluşturma zamanı . Tüm ekranı Sunucu Odaklı Kullanıcı Arayüzü üzerinde uygulamaya karar vermeniz önemlidir ve bu ekran, uygulamanızda bir yerde açılacaktır.


Yani bu ekran çok ağırsa, yerel Flutter ekranının bile oluşturulması uzun zaman alacaktır, bu nedenle böyle bir ekrana geçerken, özellikle bu geçişe bir animasyon eşlik ediyorsa gecikmeler görülebilir. İkinci senaryo , dinamik kullanıcı arayüzü değişiklikleriyle kare süresidir (FPS) . Veriler değişti; bazı bileşenleri yeniden çizmeniz gerekiyor. Sorun bunun render süresini ne kadar etkileyeceği, ekran güncellendiğinde kullanıcının gecikmeler göreceği kadar etkileyip etkilemeyeceğidir. Ve işte başka bir spoiler - çoğu durumda gördüğünüz ekranın tamamen Nui'ye uygulandığını söyleyemezsiniz. Normal, yerel bir Flutter ekranına bir Nui widget'ı yerleştirirseniz (örneğin, uygulamada çok dinamik olarak değişmesi gereken ekranın bir kısmı), bunu tanıyamayacağınız garanti edilir. Performansta elbette düşüşler var. Ancak 120FPS kare hızında bile FPS'yi etkilemeyecek şekildedirler - yani bir karenin süresi neredeyse hiçbir zaman 8ms geçmeyecektir. Bu ikinci senaryo için de geçerlidir. İlkine gelince, hepsi ekranın karmaşıklık seviyesine bağlıdır. Ancak burada bile fark öyle olacaktır ki algıyı etkilemeyecek ve uygulamanızı kullanıcı akıllı telefonları için bir referans haline getirmeyecektir.


Aşağıda Pixel 7a'dan üç ekran kaydı bulunmaktadır (Tensor G2, ekran yenileme hızı 90 kareye ayarlanmıştır (bu cihaz için maksimum), video kayıt hızı saniyede 60 kare (kayıt ayarları için maksimum). Her 500 ms'de, öğelerin konumu liste, ilk 3 kartın oluşturulduğu verilerden rastgele seçilir ve 500 ms daha sonra sipariş durumu bir sonrakine geçer. Bu ekranlardan hangisinin tamamen Nui'de uygulandığını tahmin edebilecek misiniz?


Not: Görüntülerin yüklenme süresi uygulamaya bağlı değildir, çünkü bu ekranda herhangi bir uygulamada çok sayıda Svg görüntüsü vardır - tüm simgeler ve marka logoları. Tüm svg'ler (normal resimlerin yanı sıra) GitHub'da bir barındırma olarak depolanır, bu nedenle bazı deneylerde gözlemlendiği gibi oldukça yavaş yüklenebilirler.


Youtube:


Kullanılabilir Bileşenler - Kullanıcı Arayüzü Nasıl Oluşturulur

Nui'yi oluştururken aşağıdaki konsepte bağlı kaldım - öyle bir araç yaratmak gerekiyor ki, her şeyden önce Flutter geliştiricileri onu normal Flutter uygulamaları oluşturmak kadar kolay bulacaklar. Bu nedenle, tüm bileşenleri adlandırma yaklaşımı basitti; onları Flutter'daki adlarıyla aynı şekilde adlandırmak.


Aynısı widget parametreleri için de geçerlidir - parametre olarak kendileri yapılandırılmayan String , int , double , enum vb. gibi skalerler. Nui içindeki bu tür parametrelere argümanlar denir. Ve Container widget'ındaki decoration gibi, özellik adı verilen karmaşık sınıf parametrelerine. Bazı özellikler çok ayrıntılı olduğundan bu kural mutlak değildir, dolayısıyla adları basitleştirilmiştir. Ayrıca bazı widget'lar için mevcut parametrelerin listesi genişletildi. Örneğin, bir kare SizedBox veya Container oluşturmak için, iki aynı width + height yerine yalnızca bir özel size argümanını iletebilirsiniz.


Uygulanan widget'ların tam listesini vermeyeceğim, çünkü bunlardan epeyce var (şu anda 53). Kısacası, prensipte bir yaklaşım olarak Sunucu Odaklı Kullanıcı Arayüzünü kullanmanın mantıklı olacağı hemen hemen her kullanıcı arayüzünü uygulayabilirsiniz. Slivers ilişkili karmaşık kaydırma efektleri dahil.


Uygulanan widget'lar



Ayrıca bileşenlerle ilgili olarak, bulut XML kodunu iletmeniz gereken giriş noktasına veya widget'a dikkat etmek önemlidir. Şu anda bu tür iki widget var - NuiListWidget ve NuiStackWidget .


Tüm ekranı uygulamanız gerekiyorsa, tasarım gereği ilki kullanılmalıdır. Kaputun altında, orijinal işaretleme kodundan ayrıştırılacak tüm widget'ları içeren bir CustomScrollView bulunur. Dahası, ayrıştırmanın "akıllı" olduğu söylenebilir: CustomScrollView içeriğinin slivers olması gerektiğinden, olası bir çözüm, akıştaki widget'ların her birini bir SliverToBoxAdapter içine sarmak olacaktır, ancak bunun son derece olumsuz bir etkisi olacaktır. performans üzerine. Bu nedenle, widget'lar ebeveynlerine şu şekilde gömülür - ilkinden başlayarak, gerçek bir sliver karşılaşana kadar listede aşağı ineriz. Bir sliver karşılaştığımızda, önceki tüm widget'ları SliverList ve onu ana CustomScrollView ekliyoruz. Böylece, slivers sayısı minimum düzeyde olacağından kullanıcı arayüzünün tamamını oluşturma performansı mümkün olduğu kadar yüksek olacaktır. CustomScrollView çok fazla slivers olması neden kötüdür? Cevap burada .


İkinci widget - NuiStackWidget aynı zamanda tam ekran olarak da kullanılabilir - bu durumda, oluşturduğunuz her şeyin Stack aynı sırayla yerleştirileceğini akılda tutmakta fayda var. Ayrıca slivers açıkça kullanmak da gerekli olacaktır - yani, slivers bir listesini istiyorsanız - CustomScrollView eklemeniz ve listeyi zaten bunun içine uygulamanız gerekecektir.


İkinci senaryo, yerel bileşenlere yerleştirilebilecek küçük bir widget'ın uygulanmasıdır. Diyelim ki sunucunun inisiyatifinde tamamen özelleştirilebilecek bir ürün kartı yapmak. Nui'yi kullanarak bileşen kitaplığındaki tüm bileşenleri uygulayabileceğiniz ve bunları normal widget'lar olarak kullanabileceğiniz çok ilginç bir senaryo gibi görünüyor. Aynı zamanda uygulamayı güncellemeden bunları tamamen değiştirme imkanı da her zaman olacaktır.


NuiListWidget ekranın tamamı olarak değil, yerel bir widget olarak da kullanılabileceğini belirtmekte fayda var, ancak bu widget için ana widget için açık bir yükseklik ayarlamak gibi uygun kısıtlamaları uygulamanız gerekecektir.


Flutter kullanılarak oluşturulmuş olsaydı bir counter app şöyle görünürdü:

 import 'package:flutter/material.dart'; import 'package:nui/nui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Nui App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Nui Demo App'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({ required this.title, super.key, }); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: NuiStackWidget( renderers: const [], imageErrorBuilder: null, imageFrameBuilder: null, imageLoadingBuilder: null, binary: null, nodes: null, xmlContent: ''' <center> <column mainAxisSize="min"> <text size="18" align="center"> You have pushed the button\nthis many times: </text> <text size="32"> {{ page.counter }} </text> </column> </center> ''', pageData: { 'counter': _counter, }, ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }


Ve işte tamamen Nui'de (mantık dahil) başka bir örnek:

 import 'package:flutter/material.dart'; import 'package:nui/nui.dart'; void main() { runApp(const MyApp()); } final DataStorage globalDataStorage = DataStorage(data: {'counter': 0}); final EventHandler counterHandler = EventHandler( test: (BuildContext context, Event event) => event.event == 'increment', handler: (BuildContext context, Event event) => globalDataStorage.updateValue( 'counter', (globalDataStorage.getTypedValue<int>(query: 'counter') ?? 0) + 1, ), ); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return DataStorageProvider( dataStorage: globalDataStorage, child: EventDelegate( handlers: [ counterHandler, ], child: MaterialApp( title: 'Nui App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Nui Counter'), ), ), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({ required this.title, super.key, }); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: NuiStackWidget( renderers: const [], imageErrorBuilder: null, imageFrameBuilder: null, imageLoadingBuilder: null, binary: null, nodes: null, xmlContent: ''' <center> <column mainAxisSize="min"> <text size="18" align="center"> You have pushed the button\nthis many times: </text> <dataBuilder buildWhen="counter"> <text size="32"> {{ data.counter }} </text> </dataBuilder> </column> </center> <positioned right="16" bottom="16"> <physicalModel elevation="8" shadowColor="FF000000" clip="antiAliasWithSaveLayer"> <prop:borderRadius all="16"/> <material type="button" color="EBDEFF"> <prop:borderRadius all="16"/> <inkWell onPressed="increment"> <prop:borderRadius all="16"/> <tooltip text="Increment"> <sizedBox size="56"> <center> <icon icon="mdi_plus" color="21103E"/> </center> </sizedBox> </tooltip> </inkWell> </material> </physicalModel> </positioned> ''', pageData: {}, ), ), ); } }


UI kodunu vurgulanacak şekilde ayırın:

 <center> <column mainAxisSize="min"> <text size="18" align="center"> You have pushed the button\nthis many times: </text> <dataBuilder buildWhen="counter"> <text size="32"> {{ data.counter }} </text> </dataBuilder> </column> </center> <positioned right="16" bottom="16"> <physicalModel elevation="8" shadowColor="black" clip="antiAliasWithSaveLayer"> <prop:borderRadius all="16"/> <material type="button" color="EBDEFF"> <prop:borderRadius all="16"/> <inkWell onPressed="increment"> <prop:borderRadius all="16"/> <tooltip text="Increment"> <sizedBox size="56"> <center> <icon icon="mdi_plus" color="21103E"/> </center> </sizedBox> </tooltip> </inkWell> </material> </physicalModel> </positioned> 

Nui mantığına sahip Nui Counter Uygulaması


Ayrıca, her bir widget'in hangi argümanlara ve özelliklere sahip olduğu ve bunların tüm olası değerleri hakkında ayrıntılı bilgi gösteren etkileşimli ve kapsamlı belgeler de bulunmaktadır. Hem argümanlara hem de başka özelliklere sahip olabilen özelliklerin her biri için, mevcut tüm değerlerin tam gösterimini içeren belgeler de mevcuttur. Buna ek olarak, bileşenlerin her biri, bu widget'ın uygulamasını canlı olarak görebileceğiniz ve istediğiniz gibi değiştirerek onunla oynayabileceğiniz etkileşimli bir örnek içerir.

Nanc Oyun Alanı

Nui, Nanc CMS'ye çok sıkı bir şekilde entegre edilmiştir. Nui'yi kullanmak için Nanc'ı kullanmak zorunda değilsiniz, ancak Nanc'ı kullanmak size avantajlar sağlayabilir: aynı etkileşimli belgelerin yanı sıra düzenin sonuçlarını gerçek zamanlı olarak görebileceğiniz, verilerle oynayabileceğiniz Oyun Alanı bu onun içinde kullanılacaktır. Üstelik kendi yerel CMS yapınızı oluşturmanıza gerek yok, ihtiyacınız olan her şeyi yapabileceğiniz yayınlanan demoyu oldukça yönetebilirsiniz.


Bunu, bağlantıyı takip ederek ve ardından Page Interface / Screen alanına tıklayarak yapabilirsiniz. Açılan ekran Oyun Alanı olarak kullanılabilir ve Sync butonuna tıklayarak, Nanc'ı IDE'niz ile kaynaklar içeren bir dosya üzerinden senkronize edebilirsiniz ve Help butonuna tıklayarak tüm belgelere ulaşabilirsiniz.


Not: Bu karmaşıklıklar, Nanc'taki bileşenlerle ilgili belgeleri içeren açık ve ayrı bir sayfa oluşturacak zamanı bulamadığım ve bu sayfaya doğrudan bir bağlantı ekleyemediğim için var.


Etkileşim ve Mantık

XML'den widget'lara kadar sıradan bir eşleyici oluşturmak çok anlamsız olurdu. Bu elbette faydalı da olabilir, ancak çok daha az kullanım durumu olacaktır. Aynı şey değil - etkileşimde bulunabileceğiniz, ayrıntılı olarak güncelleyebileceğiniz tamamen etkileşimli bileşenler ve ekranlar (yani hepsini aynı anda değil - ancak güncellenmesi gereken parçalar halinde). Ayrıca bu kullanıcı arayüzünün verilere ihtiyacı var. Bu, Sunucu Odaklı Kullanıcı Arayüzü ifadesinde S harfinin varlığı dikkate alındığında, doğrudan sunucudaki düzende değiştirilebilir, ancak bunu daha güzel bir şekilde de yapabilirsiniz. Ve kullanıcı arayüzündeki her değişiklik için düzenin yeni bir bölümünü arka uçtan sürüklememek (Nui, jQuery'nin en iyi uygulamalarını hızla getiren bir zaman makinesi değildir).


Mantıkla başlayalım: değişkenler ve hesaplanan ifadeler düzende değiştirilebilir. Diyelim ki <container color="{{ page.background }}"> olarak tanımlanan bir widget, rengini doğrudan background plan değişkeninde depolanan "ana bağlam"a aktarılan verilerden çıkaracaktır. Ve <aspectRatio ratio="{{ 3 / 4}}"> alt öğeleri için karşılık gelen en boy oranı değerini ayarlayacaktır. Bazı mantıklarla kullanıcı arayüzü oluşturmak için kullanılabilecek yerleşik işlevler, karşılaştırmalar ve çok daha fazlası vardır.


İkinci nokta şablonlamadır . <template id="your_component_name"/> etiketini kullanarak kendi widget'ınızı doğrudan kullanıcı arayüzü kodunda tanımlayabilirsiniz. Aynı zamanda, bu şablonun tüm dahili bileşenleri, bu şablona iletilen bağımsız değişkenlere erişime sahip olacak; bu, özel bileşenlerin esnek parametrelendirilmesine ve daha sonra <component id="your_component_name"/> etiketi kullanılarak yeniden kullanılmasına olanak tanıyacak. Şablonların içinde yalnızca nitelikleri değil aynı zamanda diğer etiketleri/widget'ları da iletebilirsiniz; bu da her türlü karmaşıklıkta yeniden kullanılabilir bileşenler oluşturmayı mümkün kılar.


Üçüncü nokta - "döngüler için". Nui'de, aynı (veya birden fazla) bileşeni birden çok kez oluşturmak için yinelemeler kullanmanıza olanak tanıyan yerleşik bir <for> etiketi vardır. Bu, widget'ların bir listesini/satırını/sütununu oluşturmanız gereken bir veri kümesi olduğunda kullanışlıdır.


Dördüncü - koşullu oluşturma. Düzen düzeyinde, iç içe geçmiş bileşenler çizmenize veya bunları çeşitli koşullar altında ağaca hiç yerleştirmemenize olanak tanıyan <show> etiketi uygulanır (buna <if> adını verme fikri vardı).


Beşinci nokta - eylemler. Kullanıcının etkileşim kurabileceği bazı bileşenler etkinlik gönderebilir . Tamamen istediğiniz gibi kontrol edebileceğiniz. Diyelim ki, <inkWell onPressed="something"> - böyle bir bildirimle bu widget tıklanabilir hale gelir ve uygulamanız veya daha doğrusu bazı EventHandler bu olayı işleyebilir ve bir şeyler yapabilir. Buradaki fikir, mantıkla ilgili her şeyin doğrudan uygulamada uygulanması gerektiğidir, ancak her şeyi uygulayabilirsiniz. "Ekrana git" / "çağrı yöntemi" / "analitik olayı gönder" gibi eylem gruplarını işleyebilecek bazı genel işleyiciler yapın. Dinamik kodun uygulanmasına yönelik planlar da var ancak burada bazı nüanslar var. Dart için rastgele kod çalıştırmanın yolları vardır, ancak bu performansı etkiler ve ayrıca bu kodun uygulama koduyla birlikte çalışabilirliği neredeyse %100 değildir. Yani bu dinamik kodda mantık oluşturarak sürekli olarak bazı sınırlamalarla karşılaşacaksınız. Dolayısıyla bu mekanizmanın gerçekten uygulanabilir ve faydalı olabilmesi için çok dikkatli bir şekilde çalışılması gerekiyor.


Altıncı nokta yerel kullanıcı arayüzü güncellemesidir. Bu <dataBuilder> etiketi sayesinde mümkündür. Bu etiket (başlık altındaki Blok) belirli bir alana "bakabilir" ve değiştiğinde alt ağacını yeniden çizer.


Veri

Başlangıçta, veriler için iki deponun yolunu izledim - yukarıda bahsedilen "ana bağlam". "Veri"nin yanı sıra, <data> etiketi kullanılarak doğrudan kullanıcı arayüzünde tanımlanabilen veriler. Dürüst olmak gerekirse, verileri depolamanın ve kullanıcı arayüzüne aktarmanın iki yolunu uygulamanın neden gerekli olduğuna dair tartışmayı şimdi hatırlayamıyorum, ancak böyle bir karar için kendimi bir şekilde sert bir şekilde eleştiremiyorum.


Aşağıdaki gibi çalışırlar - "ana bağlam" Map<String, dynamic> türünde bir nesnedir ve doğrudan NuiListWidget / NuiStackWidget widget'larına aktarılır. Bu verilere erişim ön ek page mümkündür:

 <someWidget value="{{ page.your.field }}"/>

Diziler dahil her şeye, her derinliğe başvurabilirsiniz - {{ page.some.array.0.users.35.age }} . Böyle bir anahtar/değer yoksa null alırsınız. Listeler <for> kullanılarak yinelenebilir.


İkinci yol - "veri" küresel bir veri deposudur. Pratikte bu, ağaçta NuiListWidget / NuiStackWidget daha yüksekte bulunan belirli bir Bloc . Aynı zamanda, kendi DataStorage örneğinizi DataStorageProvider aracılığıyla ileterek kullanımlarını yerel bir tarzda düzenlemeyi hiçbir şey engellemez.


Aynı zamanda, ilk yöntem reaktif değildir; yani page veriler değiştiğinde hiçbir kullanıcı arayüzü kendini güncellemez. Çünkü bu aslında sadece StatelessWidget argümanları. page veri kaynağı, örneğin Nui...Widget bir dizi değer verecek olan kendi Bloc'unuzsa, normal bir StatelessWidget olduğu gibi, yeni verilerle tamamen yeniden çizilecektir.


Verilerle çalışmanın ikinci yolu reaktiftir. DataStorage içindeki verileri, bu sınıfın API'sini ( updateValue yöntemini) kullanarak değiştirirseniz, bu, Bloc sınıfının emit yöntemini çağırır ve kullanıcı arayüzünüzde - <dataBuilder> etiketlerinde bu verilerin aktif dinleyicileri varsa, o zaman içerikleri buna göre değiştirilecek ancak kullanıcı arayüzünün geri kalanına dokunulmayacak.


Böylece iki potansiyel veri kaynağı elde ederiz: çok basit bir page ve reaktif data . Bu kaynaklardaki verilerin güncellenmesi mantığı ve kullanıcı arayüzünün bu güncellemelere tepkisi dışında aralarında hiçbir fark yoktur.

Dokümantasyon

Zaten var olan belgelerin bir kopyası olacağı için çalışmanın tüm nüanslarını ve yönlerini kasıtlı olarak açıklamadım. Bu nedenle, daha fazlasını denemek veya öğrenmek ilginizi çekerse lütfen buraya hoş geldiniz. Çalışmanın herhangi bir yönü açık değilse veya belgeler bir şeyi kapsamıyorsa, sorunu belirten mesajınız beni gururlandıracaktır:


Bu makalede ele alınmayan ancak kullanımınıza sunulan bazı özellikleri kısaca listeleyeceğim:

  • Kendi etiketlerinizi/bileşenlerinizi, tıpkı canlı önizleme ile argümanları ve özellikleri için olduğu gibi, bunlar için tam olarak aynı etkileşimli belgeleri oluşturma olanağıyla oluşturma. Örneğin SVG görüntülerinin oluşturulmasına yönelik bileşen bu şekilde uygulanır. Bunu motorun çekirdeğine itmenin bir anlamı yok, çünkü herkesin buna ihtiyacı yok, ancak bir uzantı olarak tek bir değişkeni geçirerek kullanıma hazır - kolay ve basit. Doğrudan -bir uygulama örneği .


  • Kendinizinkileri ekleyerek genişletilebilen devasa bir yerleşik simge kitaplığı (burada tutarsız çıktım ve "ittim", mantık mümkün olduğu kadar çok simgeyi hemen kullanıma hazır hale getirmekti ve buna gerek yoktu) yeni simgeleri kullanacak şekilde uygulamayı güncelleyin). Kutudan çıktığı gibi şu seçenekler mevcuttur: fluentui_system_icons , materyal_design_icons_flutter ve remixicon . Mevcut tüm simgeleri Nanc , Page Interface / Screen -> Icons kullanarak görüntüleyebilirsiniz.

  • Kutudan çıkan Google Yazı Tipleri de dahil olmak üzere özel yazı tipleri


  • XML'i JSON/protobuf'a dönüştürme ve bunları kullanıcı arayüzü için "kaynaklar" olarak kullanma


Bütün bunlar ve çok daha fazlası belgelerde incelenebilir.


Sıradaki ne?

Önemli olan, kodu dinamik olarak mantıkla yürütme olasılığını çözmektir. Bu, Nui'nin yeteneklerini çok ciddi şekilde genişletmenize olanak sağlayacak çok harika bir özellik. Ayrıca, standart Flutter kütüphanesinden kalan nadiren kullanılan, ancak bazen çok önemli widget'ları da ekleyebilirsiniz (ve eklemelisiniz). XSD'de uzmanlaşmak, böylece tüm etiketler için otomatik tamamlama IDE'de görünür (bu şemayı doğrudan etiket belgelerinden oluşturma fikri vardır, o zaman bunu özel widget'lar için oluşturmak kolay olacak ve her zaman güncel olacaktır) -date ve ayrıca Dart'ta daha sonra XML / Json / Protobuf'a dönüştürülebilecek bir DSL oluşturma fikri de var. Peki, ek performans optimizasyonu - şu anda fena değil, çok da kötü değil, ama daha da iyi olabilir, hatta yerel Flutter'a daha yakın olabilir.


Sahip olduğum tek şey bu. Bir sonraki yazımda Nui'nin performansını, test senaryolarını nasıl oluşturduğumu, bu süreçte kaç düzinelerce komisyondan geçtiğimi, hangi senaryolarda hangi rakamların elde edilebileceğini çok detaylı bir şekilde anlatacağım.


Nui'yi denemek veya onu daha iyi tanımak ilginizi çekerse lütfen dokümantasyon masasına gidin. Ayrıca, eğer zor değilse, lütfen GitHub'a bir yıldız koyun ve pub.dev'e bir benzer ekleyin - bu sizin için zor değil, ama benim için, bu devasa teknede yalnız bir kürekçi için - inanılmaz derecede faydalı.