作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Nemanja Stošić的头像

Nemanja Stošić

Nemanja曾为创业公司和大公司工作,尤其是微软. 他是敏捷/Scrum专家,担任过团队领导和导师.

Previously At

Microsoft
Share

What is Flutter?

Flutter是Google的移动应用开发SDK,允许你的产品同时瞄准Android和iOS平台, without the need to maintain two separate codebases. 此外,使用Flutter的应用程序也可以针对谷歌即将推出的应用程序进行编译 Fuchsia操作系统.

Flutter recently hit a major milestone - stable version 1.0. The release took place in London, December 5th, 2018, at the 扑动现场活动. 虽然它仍然可以被视为一个早期的和不断发展的软件企业, 本文将重点介绍一个已经得到验证的概念,并演示如何开发一个功能齐全的消息传递应用程序,该应用程序针对两个主要的移动平台 Flutter 1.2 and Firebase.

如下图所示,Flutter在最近几个月获得了大量用户. In 2018, Flutter的市场份额翻了一番,在搜索查询方面也有望超过React Native, hence our decision to create a new Flutter tutorial.

图表比较了2018年7月至9月的Flutter和React用户.

Note: This article focuses only on certain bits of the implementation. Full source code reference for the project can be found in 这个GitHub仓库.

Prerequisites

即使这是读者第一次尝试移动开发,我们也会努力让读者跟随并完成这个项目, 许多核心的手机开发概念并不是专门针对《欧博体育app下载》的,但却没有给出详细的解释.

这样做是为了文章的简洁,因为它的目标之一是让读者一次完成这个项目. Finally, 本文假设您已经设置好了开发环境, including the required Android Studio plugins and Flutter SDK.

Firebase Set Up

设置Firebase是我们必须为每个平台独立做的唯一事情. First of all, make sure you create a new project in the 重火力点仪表板 并在新生成的工作区中添加Android和iOS应用程序. 该平台将生成您需要下载的两个配置文件: google-services.json for Android and GoogleService-Info.plist for iOS. 在关闭仪表板之前, 请确保启用Firebase和Google身份验证提供商,因为我们将使用它们来进行用户身份验证. 为此,请从菜单中选择Authentication项,然后选择Sign-In方法选项卡.

现在您可以关闭仪表板,因为其余的设置将在代码库中进行. 首先,我们需要把下载的文件放到我们的项目中. The google-services.json 文件应放在 $ (FLUTTER_PROJECT_ROOT) / android应用程序 folder and GoogleService-Info.plist 应该放在 $ (FLUTTER_PROJECT_ROOT) / ios /跑步者 directory. Next, 我们需要实际设置项目中将要使用的Firebase库,并将它们与配置文件连接起来. 这是通过指定我们将在项目中使用的Dart包(库)来完成的 pubspec.yaml file. 在文件的依赖项部分,粘贴以下代码片段:

flutter_bloc:
shared_preferences:
firebase_auth:
cloud_firestore:
google_sign_in:
flutter_facebook_login:

前两个与Firebase无关,但将在项目中频繁使用. The last two are, hopefully, self-explanatory.

Finally, 我们需要配置特定于平台的项目设置,使我们的身份验证流能够成功完成. 在Android端, 我们需要将google-services Gradle插件添加到我们的项目级Gradle配置中. 换句话说,我们需要将以下项添加到中的依赖项列表中 $(FLUTTER_PROJECT_ROOT)/android/build.gradle file:

classpath 'com.google.gms:谷歌服务:4.2.0' // change 4.2.0到最新版本

然后我们需要通过将这一行添加到末尾来应用该插件 $ (FLUTTER_PROJECT_ROOT) / android应用程序/build.gradle:

应用插件:'com.google.gms.谷歌服务的

该平台的最后一件事是登记您的Facebook应用程序参数. What we are looking for here is editing these two files - $ (FLUTTER_PROJECT_ROOT) / android应用程序/src/main/AndroidManifest.xml and $ (FLUTTER_PROJECT_ROOT) / android应用程序/src/main/res/values/strings.xml:


 

 
        
        
                
                    
                    
                    
                    
                
        
 
                                                                           
                                                                           

    

 


   Toptal Chat
   ${YOUR_FACEBOOK_APP_ID}
   ${YOUR_FACEBOOK_URL}

现在是时候推出iOS了. Luckily, we only need to change one file in this case. 添加以下值(请注意 CFBundleURLTypes item may already exist in the list; in that case, 您需要将这些项添加到现有数组中,而不是再次声明它 $(FLUTTER_PROJECT)ROOT/ios/Runner/Info.plist file:

CFBundleURLTypes

  
     CFBundleURLSchemes
     
        ${YOUR_FACEBOOK_URL}
     
  
  
     CFBundleTypeRole
     Editor
     CFBundleURLSchemes
     
        ${YOUR_REVERSED_GOOGLE_WEB_CLIENT_ID}
     
  

FacebookAppID
${YOUR_FACEBOOK_APP_ID}
FacebookDisplayName
${YOUR_FACEBOOK_APP_NAME}
LSApplicationQueriesSchemes

  fbapi
  fb-messenger-share-api
  fbauth2
  fbshareextension

浅谈集团架构

在我们之前的一篇文章中描述了这个体系结构标准,演示了 BLoC for code sharing in Flutter and AngularDart, so we won’t be explaining it in detail here.

主要思想背后的基本思想是,每个屏幕都有以下类: - view -它负责显示当前状态并将用户输入作为事件委托给bloc. - state -它表示用户使用当前视图与之交互的“实时”数据. - bloc - which responds to events and updates the state accordingly, 可选地从一个或多个本地或远程存储库请求数据. - event -这是一个确定的动作结果,可能会也可能不会改变当前状态.

As a graphic representation, it can be thought of like this:

Flutter教程:BLoC架构的图形表示.

另外,我们有 model 目录,其中包含数据类和生成这些类实例的存储库.

UI Development

Creating UI using Flutter is done completely in Dart, 与Android和iOS的原生应用开发相反,Android和iOS的UI是使用XML模式构建的,与业务逻辑代码库完全分离. 我们将使用相对简单的UI元素组合,根据当前状态使用不同的组件.g. isLoading, isEmpty参数). Flutter中的UI围绕着小部件,或者说是小部件树. Widgets can either be stateless or stateful. 当涉及到有状态时,强调这一点很重要 setState() 在当前显示的特定小部件上调用(在构造函数中调用它或在它被处置后调用它会导致运行时错误)。, 计划在下一个绘图周期中执行构建和绘制阶段.

For brevity, we’ll only show one of the UI (view) classes here:

class LoginScreen extends StatefulWidget {
 LoginScreen({Key key}) : super(key: key);
 
 @override
 State createState() => _LoginState();
}
 
class _LoginState extends State {
 final _bloc = LoginBloc();
 
 @override
 小部件构建(BuildContext) {
   return BlocProvider(
     bloc: _bloc,
     child: LoginWidget(widget: widget, widgetState: this)
   );
 }
 
 @override
 Void dispose() {
   _bloc.dispose();
   super.dispose();
 }
}
 
class LoginWidget extends StatelessWidget {
 const LoginWidget({Key key, @required this.小部件,@需要这个.widgetState}): super(key: key);
 
 最后的LoginScreen小部件;
 最后_LoginState;
 
 @override
 小部件构建(BuildContext) {
   return Scaffold(
     appBar: AppBar(
       标题:文本(“登录”),
     ),
     身体:BlocBuilder (
         集团:BlocProvider.of(context),
         builder: (context, LoginState state) {
           if (state.loading) {
             return Center(
                 child: CircularProgressIndicator(strokeWidth: 4.0)
             );
           } else {
             return Center(
               child: Column(
                 mainAxisAlignment: mainAxisAlignment.center,
                 crossAxisAlignment: CrossAxisAlignment.center,
                 children: [
                   ButtonTheme(
                     minWidth: 256.0,
                     height: 32.0,
                     孩子:RaisedButton (
                       onPressed: () => BlocProvider.of(context).onLoginGoogle(这个),
                       child: Text(
                         “登录谷歌”,
                         style: TextStyle(color:彩色.white),
                       ),
                       color: Colors.redAccent,
                     ),
                   ),
                   ButtonTheme(
                     minWidth: 256.0,
                     height: 32.0,
                     孩子:RaisedButton (
                       onPressed: () => BlocProvider.of(context).onLoginFacebook(这个),
                       child: Text(
                         “Login with Facebook”,
                         style: TextStyle(color:彩色.white),
                       ),
                       color: Colors.blueAccent,
                     ),
                   ),
                 ],
               ),
             );
           }
         }),
   );
 }
 
 void navigateToMain() {
     NavigationHelper.navigateToMain (widgetState.context);
 }
}

其余的UI类遵循相同的模式,但可能具有不同的操作,并且除了加载状态之外可能还具有一个空状态小部件树.

Authentication

As you may have guessed, we’ll be using google_sign_in and flutter_facebook_login 图书馆通过依赖用户的社会网络配置文件来验证用户. First of all, 确保将这些包导入到将要处理登录请求逻辑的文件中:

import 'package:flutter_facebook_login/flutter_facebook_login.dart';
import 'package:google_sign_in/google_sign_in.dart';

Now, 我们将有两个独立的部分来处理我们的身份验证流. 第一个将启动Facebook或Google登录请求:

void onLoginGoogle(LoginWidget view) async {
    调度(LoginEventInProgress ());
    final googleSignInRepo = GoogleSignIn(signInOption: SignInOption.standard, scopes: ["profile", "email"]);
    final account = await googleSignInRepo.signIn();
    if (account != null) {
        LoginRepo.getInstance().signInWithGoogle(账户);
    } else {
        调度(LogoutEvent ());
    }
}
 
void onLoginFacebook(LoginWidget view) async {
    调度(LoginEventInProgress ());
    final facebookSignInRepo = FacebookLogin();
    final signInResult = await facebookSignInRepo.logInWithReadPermissions(("电子邮件"));
    if (signInResult.status == FacebookLoginStatus.loggedIn) {
        LoginRepo.getInstance().signInWithFacebook (signInResult);
    } else if (signInResult.status == FacebookLoginStatus.cancelledByUser) {
        调度(LogoutEvent ());
    } else {
        dispatch(LoginErrorEvent(signInResult.errorMessage));
    }
}

当我们从任一提供程序获得概要数据时,将调用第二个. 我们将通过指示登录处理程序监听来完成此操作 firebase_auth onAuthStateChange stream:

void _setupAuthStateListener(LoginWidget view) {
 if (_authStateListener == null) {
   _authStateListener = FirebaseAuth.instance.onAuthStateChanged.listen((user) {
     if (user != null) {
       最终loginProvider =用户.providerId;
       UserRepo.getInstance().setCurrentUser(用户.fromFirebaseUser(用户));
       if (loginProvider == "google") {
         // TODO analytics call for google login provider
       } else {
         // TODO analytics call for facebook login provider
       }
       view.navigateToMain ();
     } else {
       调度(LogoutEvent ());
     }
   }, onError: (error) {
     调度(LoginErrorEvent(错误));
   });
 }
}

The UserRepo and LoginRepo 实现将不会张贴在这里,但请随意看看 the GitHub repo 供全面参考.

Flutter Tutorial: How to Build an Instant Messaging App

Finally, we get to the interesting part. 顾名思义,应该尽可能快地交换消息,理想情况下应该是这样 instant. Luckily, cloud_firestore 允许我们与Firestore实例进行交互,我们可以使用它的 snapshots() 功能打开数据流,将给我们实时更新. 在我看来,所有的 chat_repo code is pretty straightforward with the exception of the startChatroomForUsers method. 它负责为两个用户创建一个新的聊天室,除非存在一个包含两个用户的现有聊天室(因为我们不希望有同一用户对的多个实例),在这种情况下,它返回现有的聊天室.

然而,由于Firestore的设计,它目前不支持嵌套 array-contains queries. 因此,我们无法检索适当的数据流,而需要在我们这边执行额外的过滤. 该解决方案包括检索登录用户的所有聊天室,然后搜索也包含所选用户的聊天室:

Future startChatroomForUsers(List users) async {
 DocumentReference userRef = _firestore
     .收集(FirestorePaths.USERS_COLLECTION)
     .文档(用户[1].uid);
 QuerySnapshot queryResults = await _firestore
     .收集(FirestorePaths.CHATROOMS_COLLECTION)
     .where("participants", arrayContains: userRef)
     .getDocuments();
 DocumentReference otherUserRef = _firestore
     .收集(FirestorePaths.USERS_COLLECTION)
     .文档(用户[0].uid);
 DocumentSnapshot roomSnapshot = queryResults.documents.firstWhere((房间){
   return room.数据(“参与者”).包含(otherUserRef);
 }, orElse: () => null);
 如果(roomSnapshot != null) {
   返回SelectedChatroom (roomSnapshot.documentID,用户[0].displayName);
 } else {
   Map chatroomMap = Map();
   chatroomMap["messages"] = List(0);
   List participants = List(2);
   参与者[0]= otherUserRef;
   [1] = userRef;
   chatroomMap["participants"] = participants;
   DocumentReference reference = await _firestore
       .收集(FirestorePaths.CHATROOMS_COLLECTION)
       .添加(chatroomMap);
   DocumentSnapshot chatroomSnapshot = await reference.get();
   return SelectedChatroom(chatroomSnapshot.documentID,用户[0].displayName);
 }
}

Also, 由于类似的设计约束, Firebase目前不支持数组更新(在现有数组字段值中插入新元素) FieldValue.serverTimestamp () value.

该值向平台表明,在事务发生时,应该用服务器上的实际时间戳填充包含该值(而不是实际值)的字段. 相反,我们使用 DateTime.now() 目前,我们正在创建新的消息序列化对象,并将该对象插入聊天室消息集合.

Future sendMessageToChatroom(String chatroomId, User user, String message) async {
 try {
   DocumentReference authorRef = _firestore.收集(FirestorePaths.USERS_COLLECTION).document(user.uid);
   DocumentReference chatroomRef = _firestore.收集(FirestorePaths.CHATROOMS_COLLECTION).文档(chatroomId);
   Map serializedMessage = {
     "author": authorRef;
     timestamp:日期时间.now(),
     value:消息
   };
   chatroomRef.updateData({
     messages:字段值.arrayUnion ([serializedMessage])
   });
   return true;
 } catch (e) {
   print(e.toString());
   return false;
 }
}

Wrapping Up

Obviously, 我们开发的Flutter消息应用程序更多的是一个概念验证,而不是一个市场就绪的即时消息应用程序. 作为进一步发展的想法, 可以考虑引入端到端加密或富内容(群聊), 媒体附件, URL parsing). 但在那之前, 应该实现推送通知,因为它们几乎是即时消息应用程序的必备功能, 为了简洁起见,我们把它移出了本文的范围. Additionally, Firestore仍然缺少一些功能,以便拥有更简单和更准确的数据(如嵌套) array-contains queries.

正如文章开头提到的,Flutter最近才成熟到stable 1.0 release and is going to keep growing, 不仅在框架特性和功能方面,在开发社区和第三方库和资源方面也是如此. 现在投入时间来熟悉Flutter应用程序开发是有意义的, 很明显,它会一直存在 speed up your mobile development process.

无需额外费用, 颤振的开发人员 will also be ready to target Google’s emerging OS–Fuchsia.

了解基本知识

  • Flutter的用途是什么?

    Flutter用于开发在Android和iOS上运行的应用程序,并提供相同的用户体验. Flutter还可以瞄准谷歌即将推出的操作系统Fuchsia.

  • Flutter使用哪种语言?

    Flutter的开发是在Dart中完成的,Dart是一种由Google开发的开源编程语言.

  • Flutter比React Native更好吗?

    Both Flutter and React Native deliver the same outcome, 跨平台应用, 但是它们是完全不同的,所以很难简单地指出其中一个是“更好的”。. Any comparison out of context would have very little value.

  • Flutter会取代React Native吗?

    Flutter是由谷歌拥有和开发的,这给了它很大的影响力和潜在的市场份额. 目前预计,到2019年底,中国将拥有更大的开发市场份额. 然而,这并不意味着它会在任何时候完全取代React Native.

  • What is Dart programming language used for?

    Dart是谷歌开发的, open-source, 可扩展编程语言, 具有健壮的库和运行时, for building web, server, and mobile apps.

  • Dart是一门好语言吗?

    Dart多年来经历了多次重大更新,目前支持多种范式. 这使得它非常健壮,对于许多不同的产品需求来说是一个很好的选择.

  • Dart是一种编译语言吗?

    Dart在这方面非常独特. It supports transpilation into JavaScript, a standalone VM which offers just-in-time compilation and, finally, 提前编译成平台本地指令集,从而为交付生产就绪的解决方案提供最佳性能.

聘请Toptal这方面的专家.
Hire Now
Nemanja Stošić的头像
Nemanja Stošić

Located in 温哥华,卑诗省,加拿大

Member since May 25, 2018

About the author

Nemanja曾为创业公司和大公司工作,尤其是微软. 他是敏捷/Scrum专家,担任过团队领导和导师.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

Microsoft

World-class articles, delivered weekly.

订阅意味着同意我们的 privacy policy

World-class articles, delivered weekly.

订阅意味着同意我们的 privacy policy

Toptal开发者

Join the Toptal® community.