Flutter’da Dio ile Network İletişimi

Gökhan Alp
KoçSistem
Published in
6 min readJul 29, 2021

Merhaba bu yazımda Flutter’da Network iletişimini Dio ile nasıl sağlarsınız buna değineceğim.

Dio güçlü bir Http client kütüphanesidir. Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout vs. gibi desteklere sahiptir. Dio sayesinde data çekmeniz ve yönetmeniz, gelen verileri kullanmanız daha da kolaylaşmaktadır. Dio ile size dönen responsu veya atacağınız request’i serialize ederek modele çevirebilirsiniz ve böylece daha kolay çalışabilirsiniz.

Öncelikle Dio kütüphanesini projenize nasıl ekleyeceğinize değinelim. Bunun için pubspec.yml içinde dependency bölümüne alttaki satırı ekleyip Pub Get yapıyoruz;
dio: ^4.0.0

Yazının devamı şu bölümlerden oluşacaktır;

  • Basit Request Örnekleri
  • FormData Gönderimi
  • Serialization
  • Options
  • HttpClientAdapter (Proxy ve Sertifika Doğrulama (SSL Certificate Pinning) Örnekleri)
  • Error Handling
  • Cancellation
  • Interceptors

Basit Request Örnekleri

Basit GET request örneği;

void basicGetRequest() async {
var dio = Dio();
var response = await dio.get('https://postman-echo.com/get?foo1=bar1&foo2=bar2');
print(response);
}

Yukardaki örnekte GET requesti attık ve Postman-Echo’dan requestte ilettiğimiz bilgi daha farklı bir şekilde response’da bize döndü ve bizde bu bilgiyi logta yazdırdık.

Aşağıda basit POST request örneği paylaşılmıştır. Metodun data parametresinde Dictionary olarak body’de data gönderilmesi sağlanmıştır.

void basicPostRequest() async {
var response = await Dio().post('https://postman-echo.com/post', data: {'int': 1, 'string': 'text'});
print(response);
}

İsterseniz Dio’nun multiple concurrent requests özelliği ile pratik bir şekilde sırayla request atabilirsiniz;

void concurrentRequest() async {
var dio = Dio();
var response = await Future.wait([dio.post('https://postman-echo.com/post', data: {'int': 1, 'string': 'text'}), dio.get('https://postman-echo.com/get?foo1=bar1&foo2=bar2')]);
print(response);
}

İndirme işlemleri için download request kullanılabilir. Aşağıdaki örnekteki ilk parametre web adresi ve ikinci parametre ise indirildiği zamanki path konumudur.

Aşağıdaki örnekte dosyanın indirileceği konum path_provider kütüphanesi kullanılarak ayarlanmıştır. Kütüphane linki için tıklayınız.

void downloadRequest() async {
final directory = await getApplicationDocumentsDirectory();
String filePath = directory.path + '/googleImage.png';
var response = await Dio().download('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png', filePath);
print(response);
}

Datayı Stream veya Byte olarak çekmek için aşağıdaki kod örneği kullanılabilir. Bu yöntemle bir dosya çekmenizde mümkündür.

void getWithStreamResponse() async {
Response<ResponseBody> response = await Dio().get<ResponseBody>('https://postman-echo.com/get?param=isForTesting', options: Options(responseType: ResponseType.stream));
print(response);
}

void getWithByteResponse() async {
Response<List<int>> response = await Dio().get<List<int>>('https://postman-echo.com/get?param=isForTesting', options: Options(responseType: ResponseType.bytes));
print(response);
}

Form Data Gönderimi

Dio ile isterseniz JSON yerine FormData kullanarakta request gönderebilirsiniz.

void formDataBasicSample() async {
var formData = FormData.fromMap({
'sampleType': 'formData',
'intVal': 200,
});
var response = await Dio().post('https://postman-echo.com/post', data: formData);
print(response);
}

İsterseniz birden fazla dosyayı FormData ile servise gönderme imkanına da sahipsiniz.. Alttaki örnekte dosya upload etme örneği paylaşılmıştır. Örneği oluşturabilmek adına ilk etapta 2 adet resim dosyası download edilip documents klasörüne kaydedilmiş, ikinci kod bloğunda bu 2 dosya ile birlikte FormData ile request atılması sağlanmıştır.

void formDataMultipleFileSample() async {
// Bu ilk bölümde 2 tane örnek resim örnek için indirilip documents klasörüne kaydedilecektir.
final directory = await getApplicationDocumentsDirectory();
String filePath = directory.path + '/googleImage.png';
String filePath2 = directory.path + '/luke.jpg';
await Dio().download('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png', filePath);
print("First image sample download complete!");
await Dio().download('https://www.jedbang.com/materials/images/products/products/4/4250/16118/hot-toys-luke-skywalker-crait-sixth-scale-figure-ht1-335-s0-p5-5000x5000-i16118.jpg', filePath2);
print("All image samples download complete!");

// Daha sonra indirilmiş resimler bu bölümde FormData ile gönderilecektir.
var formData = FormData.fromMap({
'sampleType': 'formData',
'intVal': 200,
'files': [
await MultipartFile.fromFile(filePath, filename: 'googleImage.png'),
await MultipartFile.fromFile(filePath2, filename: 'luke.jpeg'),
]
});
var response = await Dio().post('https://postman-echo.com/post', data: formData);
print(response);
}

Serialization

Requestlerinizi ve Responselarınızı serialize ederek json encode ve decode edebilirsiniz. Ve bu sayede modelleri kullanarak daha rahat bir şekilde request atabilir, dönen response’u modele dönüştürebilirsiniz.

Aşağıdaki kod örneğinde nasıl basitçe bunu kullanabildiğimizi göreceğiz. DataModel requestModel olacak ve Postman-Echo’da DataModel’de attığımız veriler data parametresi içinde bize geri dönüyor olacak. Servisin döndüğü responsu ise ResponseModel’e dönüştürüyor olacağız. Eğer bir modeli Json Dictionary veya String’e dönüştürerek Json halinde kullanmak isterseniz modeli .toJson() çağrısıyla dönüştürebilirsiniz, aynı şekilde Json’dan modele çevirmek için de fromJson() çağrısıyla dönüşüm yapılabilmektedir.

void serializationSample() async {
DataModel requestModel = new DataModel("aStringValue", 105);
Response response = await Dio().post('https://postman-echo.com/post', data: requestModel);
ResponseModel responseModel = ResponseModel.fromJson(response.data);
print("Please debug responseModel. " + responseModel.toString());
}

Peki gelelim bir model classı nasıl ayrlamamız gerektiğine. Alttaki örnekte DataModel, ResponseModel ve HeaderModel’e ait örnekleri birlikte paylaşıyor olacağım. DataModel hem response’da hem request’te olduğu için Jsona dönüştürme ve Json’dan modele dönüştürme örneklerini içinde barındırmaktadır. Aynı zamanda modeli manual initialize edebilmek içinde örneği mevcuttur.

Jsonda isimler farklı olsa da modelde farklı isimde kullanma imkanınız mevcuttur. Bununla alakalı örneği şurada gözlemleyebilirsiniz:
forwardedProto = json[‘x-forwarded-proto’],

Lütfen dikkat edin, ResponseModel örneğinde headers ve data parametreleri class objelerine sahip olduğundan fromJson içinde parametreler kendi içinde tekrar fromJson çekmiştir.

class DataModel {
final String strVal;
final int intVal;
// Class initialize etmek için
DataModel(this.strVal, this.intVal);
// Json'dan modele dönüştürme
DataModel.fromJson(Map<String, dynamic> json) :
strVal = json['strVal'],
intVal = json['intVal'];
// Modelden Json'a dönüştürme
Map<String, dynamic> toJson() => {
"strVal": this.strVal,
"intVal": this.intVal
};
}

class ResponseModel {
final DataModel data;
final HeadersModel headers;
final String url;

ResponseModel.fromJson(Map<String, dynamic> json) :
data = DataModel.fromJson(json['data']),
headers = HeadersModel.fromJson(json['headers']),
url = json['url'];
}

class HeadersModel {
final String forwardedProto;
final String forwardedPort;
final String amznTraceId;
final String contentLength;
final String userAgent;
final String acceptEncoding;

HeadersModel.fromJson(Map<String, dynamic> json) :
forwardedProto = json['x-forwarded-proto'],
forwardedPort = json['x-forwarded-port'],
amznTraceId = json['x-amzn-trace-id'],
contentLength = json['content-length'],
userAgent = json['user-agent'],
acceptEncoding = json['accept-encoding'];
}

Options

Options ile timeout süreleri belirleyebilir, baseUrl tanımlayabilir, header tanımlayabilir, vb. pek çok ayarlamayı pratik bir şekilde yapabilirsiniz. Böylelikle tekrar tekrar kod yazmadan nesneleri kullanarak geliştirmeler yapabilirsiniz.

Aşağıdaki örnek için bir not: Doğrudan dio nesnesinden optionsları ayarlayabilirsiniz. BaseOptions classını her seferinde yaratmak zorunda değilsiniz.

void optionsSample() async {
BaseOptions options = BaseOptions(
baseUrl: 'https://postman-echo.com',
connectTimeout: 2000,
receiveTimeout: 2000,
);
Dio dio = Dio(options);
dio.options.receiveTimeout = 1000;
dio.options.headers['accept-language'] = 'tr-TR';
var response = await dio.get('/get?foo1=bar1&foo2=bar2');
print(response);
// Response'ta gönderdiğiniz header bilgilerini görebilirsiniz..
}

HttpClientAdapter

HttpClient Http requesti gerçekleştiren gerçek bir objedir. HttpClientAdapter nesnesi Dio ve HttpClient arasında köprü görevi görür. Herhangi bir HttpClient’ı kullanmak için HttpClientAdapter’dan faydalanabilimekteyiz.

Bu adapter’ı kullanarak HttpClient’ın sahip olduğu mevcutta olan faydalı özelliklerden yararlanabiliriz. Bu özelliklerden bazıları alt başlık olarak değinilecektir;

Proxy Kullanımı

Proxy ile bir vekil sunucu aracılığıyla gitmek istediğiniz adreslere gidebilirsiniz. Kod örneği aşağıdaki gibidir;

void httpClientProxy() async {
Dio dio = Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.findProxy = (uri) {
return 'PROXY 127.0.0.1:8888';
};
};
var response = await dio.get('https://google.com');
print(response);
}

Sertifika Doğrulama (SSL Certificate Pinning)

Bu özellik ile sertifika doğrulayarak iletişim kurduğunuz adresin gerçek adres olup olmadığını kontrol edebilirsiniz; böylece daha güvenli bir şekilde iletişim kurulmasını sağlayabilirsiniz.

Dikkat: İçerik Mac bilgisayarlar içindir. Windows çözümleri için ayrıca araştırma yapınız.

Koda değinmeden önce sertifikayı nasıl dosya olarak export edeceğinize değinelim. Bunun için Terminal’i açın ve sırayla alttaki kodları giriniz. Ardından terminalin çalıştığı klasöre gidip sadece p12 uzantılı dosyayı kopyalayınız.

openssl s_client -connect google.com:443 </dev/null | openssl x509 -outform PEM -out google.pemopenssl pkcs12 -in google.pem -nokeys -passout pass:1234 -export -out google.p12

Flutter’da assets adında bir klasör oluşturup p12 uzantılı sertifika dosyasını içerisine yapıştırınız. Sonra pubspec.yml içinde asset dosyamızı tanımlayınız.

assets:
- assets/google.p12

Ardından aşağıdaki kod örneğini inceleyebilirsiniz. Oluşturduğumuz sertifika dosyasına gene kendi oluşturduğumuz şifre ile erişilmesini sağlıyoruz. Aşağıdaki yöntemde güvenli sertifikalar tanımlanmıştır. Sertifikadaki adres ve sizin o an gittiğiniz adres eşleşerek, sertifika kontrolü yapılmakta ve sizdeki sertifika ile sunucudan size gelen sertifika aynı ise iletişim kabul edilmektedir.

void httpCertificateCheck() async {
Dio dio = Dio();
ByteData certData = await rootBundle.load('assets/google.p12');
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
SecurityContext sc = SecurityContext();
sc.setTrustedCertificatesBytes(certData.buffer.asUint8List(), password: "1234");
HttpClient httpClient = HttpClient(context: sc);
return httpClient;
};
var response = await dio.get('https://google.com');
print(response);
}

Error Handling

Dio ile iletişim sırasında ne problemler yaşandığını anlamanız çok kolaydır. Soruna karşı detaylı bilgileri alma imkanına sahipsiniz. Basit bir try bloğu ile bu işi gerçekleştirebilirsiniz.

void handlingErrors() async {
Dio dio = Dio();
try {
var response = await dio.get('https://thissitenotexistssoimpossibletogotothissite.blabla.notreal.x.c.z');
print(response);
} on DioError catch (e) {
print('Type: ' + e.type.toString());
print('Message: ' + e.message);
print('Error: ' + e.error);
}

Cancellation

CancelToken ile isterseniz tek isterseniz birden fazla requestinizi aynı anda iptal edebilirsiniz. İptal için tuttuğunuz bu nesnede cancel methodunu çağırarak iptal sebebini parametre olarak vermeniz yeterli olacaktır. Alttaki örnekte ek olarak eğer iptal edilmeyle alakalı bir error yakalanırsa gerekli işin yapılması sağlanmıştır.

void cancellation() async {
final directory = await getApplicationDocumentsDirectory();
String filePath = directory.path + '/bigpicture.jpg';

CancelToken cancelToken = CancelToken();
Timer(Duration(milliseconds: 100), () {
print("Cancelling now!");
cancelToken.cancel('not_need_anymore');
});

var response = await Dio().download('https://upload.wikimedia.org/wikipedia/commons/1/11/Im_the_biggest_of_all%21_%282910772407%29.jpg', filePath, cancelToken: cancelToken)
.catchError((err){
if (CancelToken.isCancel(err)) {
print('Request canceled! Reason: '+ err.message);
}
});
print("Response received!");
print(response);
}

Interceptors

Interceptors sayesinde request atılmadan, response gelmeden ve error ile karşılaşıldığı sırada çeşitli operasyonları gerçekleştirmeniz mümkün olmaktadır. Alttaki kod örneğinde Wrapper kullanılmış olsada dilerseniz bir Interceptor classı tanımlayarak ortak operasyonlar yazmanız mümkündür. Özellikle base operasyonlar için ve error handling için interceptor kullanmak ideal bir yoldur. Çünkü interceptor sayesinde belli bir yapı oluşturabilir ve ortak business kodlarınızı her yerde kullanabilirsiniz.

void interceptorBasics() async {
Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onRequest:(options, handler){
print('Sending request to: ' + options.path);
return handler.next(options);
},
onResponse:(response,handler) {
print('Response status code: ' + response.statusCode.toString());
print('Response data: ' + response.data.toString());
return handler.next(response);
},
onError: (DioError e, handler) {
print('Error received! Message is: ' + e.message);
return handler.next(e);
}
));
await dio.get('https://postman-echo.com/get?foo1=bar1&foo2=bar2');
}

Makalemi incelediğiniz için teşekkürler. Umarım yararlı olmuştur. Beğenip, paylaşarak desteklerseniz mutlu olurum.

Kullanılan Kaynaklar ve Faydalı Linkler

--

--