Flutter Basics for Beginners (Part V)

Finally, we got to one of the most important topics, without which there is no point in going further.





The plan is pretty simple: we have to get acquainted with the client-server architecture and implement the list of posts.





In the end, we will properly organize our page files and move the list item to a separate file.





Let's fly!





Our plan
  • Part 1  - introduction to development, first appendix, concept of state;





  • Part 2  - pubspec.yaml file and using flutter on the command line;





  • Part 3  - BottomNavigationBar and Navigator;





  • Part 4 - MVC. We will use this particular pattern as one of the simplest;





  • Part 5 (current article) - http package. Creation of the Repository class, first requests, listing of posts;





  • Part 6 - working with forms, text fields and creating a post.





  • Part 7 - working with pictures, displaying pictures in the form of a grid, receiving pictures from the network, adding your own to the application;





  • Part 8 - creating your own theme, adding custom fonts and animations;





  • Part 9 - a little about testing;





Client and Server

The Client / Server model is at the heart of the entire Internet and is the most prevalent.





What is its essence?





First, let's figure out what a client and a server are:





  • Client - A user device that sends requests for the server and receives responses. It can be a smartphone, computer, or MacBook.





  • Server is a special computer that contains the data required by the user.





The whole model boils down to a primitive principle: the client sent a request, the server accepted it, processed it and transmitted the response to the client.





To organize the interaction between the server and the client, special protocols are used. At the moment, one of the most common protocols on the Internet is http / https (s stands for secure).





http / https allows you to transfer almost all known data formats: pictures, video, text.





We will be working with JSON format.





JSON is a simple and understandable data format, and most importantly, lightweight, since only text is transmitted.





JSON example:





[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  ...
]  
      
      



Here is an array of posts that we will receive from the server.





: , .





JSON :





{
  "total_items" : 1
  "result" : [
  	{
  		"id" : 1,
  		"name" : "Twillight Sparkle",
  		"pony_type" : "alicorn",
  		"friends" : [
  			"Starlight Glimmer", "Applejack", "Rarity", "Spike"
  		]
		}
  ]
}
      
      



.





.. http / https HTTP .





HTTP :





  • URL - , , . URL : https://jsonplaceholder.typicode.com/posts. ( URL' )





  • , . GET , POST , DELETE - , PUT - .





  • POST, PUT DELETE . GET URL'. : https://jsonplaceholder.typicode.com/posts/1 ( id = 1)





http



.





, pubspec.yaml



:





#  
dependencies:
  flutter:
    sdk: flutter

  #   pub-

  #    
  #    
  flutter_staggered_grid_view: ^0.4.0

  #    MVC 
  mvc_pattern: ^7.0.0

  # http     
	#    
  http: ^0.13.3
      
      



.





post.dart



models



:






//     
class Post {
  //    private
  //     
  final int _userId;
  final int _id;
  final String _title;
  final String _body;
  
  //  getters   
  //      
  int get userId => _userId;
  int get id => _id;
  String get title => _title;
  String get body => _body;

  // Dart      
  //    Post.fromJson(json) -  
  //    JSON      
  //  ,  dynamic  
  //    : String, int, double  ..
  Post.fromJson(Map<String, dynamic> json) :
    this._userId = json["userId"],
    this._id = json["id"],
    this._title = json["title"],
    this._body = json["body"];
}

// PostList     
class PostList {
  final List<Post> posts = [];
  PostList.fromJson(List<dynamic> jsonItems) {
    for (var jsonItem in jsonItems) {
      posts.add(Post.fromJson(jsonItem));
    }
  }
}

//     
//      
// 
abstract class PostResult {}

//    
class PostResultSuccess extends PostResult {
  final PostList postList;
  PostResultSuccess(this.postList);
}

//  
class PostResultFailure extends PostResult {
  final String error;
  PostResultFailure(this.error);
}

//  
class PostResultLoading extends PostResult {
  PostResultLoading();
}
      
      



.





JSON :





{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
      
      



, userId



id



, title



body



, Post.fromJson(json)



.





Repository



.





data



repository.dart



:





import 'dart:convert';

//  http 
import 'package:http/http.dart' as http;
import 'package:json_placeholder_app/models/post.dart';

//       
//  SERVER
const String SERVER = "https://jsonplaceholder.typicode.com";

class Repository {
  //      
  //   Future ,  
  // fetchPhotos  
  //     UI
  Future<PostList> fetchPosts() async {
    //   URL,  
    //    
    final url = Uri.parse("$SERVER/posts");
    //  GET 
    final response = await http.get(url);
//   
if (response.statusCode == 200) {
  //      
  // json.decode   
  return PostList.fromJson(json.decode(response.body));
} else {
  //      
  throw Exception("failed request");
}

  }
}
      
      



: , ?





.. , .





. URL .





PostController



:





import '../data/repository.dart';
import '../models/post.dart';
import 'package:mvc_pattern/mvc_pattern.dart';

class PostController extends ControllerMVC {
  //   
  final Repository repo = new Repository();

  //   
  PostController();
  
  //   -  
  PostResult currentState = PostResultLoading();

  void init() async {
    try {
      //    
      final postList = await repo.fetchPosts();
      //        
      setState(() => currentState = PostResultSuccess(postList));
    } catch (error) {
      //     
      setState(() => currentState = PostResultFailure(" "));
    }
  }


}
      
      



: :






import 'package:flutter/material.dart';
import '../controllers/post_controller.dart';
import '../models/post.dart';
import 'package:mvc_pattern/mvc_pattern.dart';

class PostListPage extends StatefulWidget {
  @override
  _PostListPageState createState() => _PostListPageState();
}

//     StateMVC
class _PostListPageState extends StateMVC {

  //    
  PostController _controller;

  //    StateMVC  
  //    
  _PostListPageState() : super(PostController()) {
    _controller = controller as PostController;
  }

  //   
  //     
  @override
  void initState() {
    super.initState();
    _controller.init();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Post List Page"),
      ),
      body: _buildContent()
    );
  }

  Widget _buildContent() {
    //     
    final state = _controller.currentState;
    if (state is PostResultLoading) {
      // 
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (state is PostResultFailure) {
      // 
      return Center(
        child: Text(
          state.error,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red)
        ),
      );
    } else {
      //   
      final posts = (state as PostResultSuccess).postList.posts;
      return Padding(
        padding: EdgeInsets.all(10),
        // ListView.builder   
        //      
        child: ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            return _buildPostItem(posts[index]);
          },
        ),
      );
    }
  }

  //   
  Widget _buildPostItem(Post post) {
    return Container(
        decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(15)),
            border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3)
        ),
        margin: EdgeInsets.only(bottom: 10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                color: Theme.of(context).primaryColor,
              ),
              padding: EdgeInsets.all(10),
              child: Text(
                post.title,
                textAlign: TextAlign.left,
                style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),),
            ),
            Container(
              child: Text(
                post.body,
                style: Theme.of(context).textTheme.bodyText2,
              ),
              padding: EdgeInsets.all(10),
            ),
          ],
        )
    );
  }

}
      
      



.





, )





:





! :





!





.





post_list_page.dart



110 , . 10 20 !





, .





.





Widget _buildItem(post)



.





:





post post_list_item.dart:











import 'package:flutter/material.dart';

import '../../models/post.dart';

//  
class PostListItem extends StatelessWidget {
  
  final Post post;
  
  //     
  PostListItem(this.post);
  
  Widget build(BuildContext context) {
    return Container(
        decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(15)),
            border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3)
        ),
        margin: EdgeInsets.only(bottom: 10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                color: Theme.of(context).primaryColor,
              ),
              padding: EdgeInsets.all(10),
              child: Text(
                post.title,
                textAlign: TextAlign.left,
                style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),),
            ),
            Container(
              child: Text(
                post.body,
                style: Theme.of(context).textTheme.bodyText2,
              ),
              padding: EdgeInsets.all(10),
            ),
          ],
        )
    );
  }
}
      
      



post_list_page.dart



:






import 'package:flutter/material.dart';
import '../../controllers/post_controller.dart';
import '../../models/post.dart';
import 'post_list_item.dart';
import 'package:mvc_pattern/mvc_pattern.dart';

class PostListPage extends StatefulWidget {
  @override
  _PostListPageState createState() => _PostListPageState();
}

//     StateMVC
class _PostListPageState extends StateMVC {

  //    
  PostController _controller;

  //    StateMVC  
  //    
  _PostListPageState() : super(PostController()) {
    _controller = controller as PostController;
  }

  //   
  //     
  @override
  void initState() {
    super.initState();
    _controller.init();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Post List Page"),
      ),
      body: _buildContent()
    );
  }

  Widget _buildContent() {
    //     
    final state = _controller.currentState;
    if (state is PostResultLoading) {
      // 
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (state is PostResultFailure) {
      // 
      return Center(
        child: Text(
          state.error,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red)
        ),
      );
    } else {
      //   
      final posts = (state as PostResultSuccess).postList.posts;
      return Padding(
        padding: EdgeInsets.all(10),
        // ListView.builder   
        //      
        child: ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            //     
            //  
            return PostListItem(posts[index]);
          },
        ),
      );
    }
  }

  
}
      
      



.





I tried to briefly tell and show by an illustrative example how to work with the network.





I hope my article has been useful to you)





Link to Github





Good code everyone!








All Articles