Android Networking


Source link: https://github.com/amitshekhariitbhu/AndroidNetworking

Fast Android Networking Library

About Fast Android Networking Library

Fast Android Networking Library is a powerful library for doing any type of networking in Android applications which is made on top of OkHttp Networking Layer.

Fast Android Networking Library takes care of each and everything. So you don't have to do anything, just make request and listen for the response.

Why use Fast Android Networking ?

  • Recent removal of HttpClient in Android Marshmallow(Android M) made other networking library obsolete.
  • No other single library do each and everything like making request, downloading any type of file, uploading file, loading image from network in ImageView, etc. There are libraries but they are outdated.
  • No other library provided simple interface for doing all types of things in networking like setting priority, cancelling, etc.
  • As it uses Okio , No more GC overhead in android application. Okio is made to handle GC overhead while allocating memory. Okio do some clever things to save CPU and memory.
  • As it uses OkHttp , most important it supports HTTP/2.

RxJava2 Support, check here.

RxJava2 + Fast Android Networking + Dagger2 with MVP Architecture Project, Check here

Another awesome library for debugging databases and shared preferences, Check here

RxJava2 + Fast Android Networking + Dagger2 with MVVM Architecture Project, Check here

Find this project useful ? ❤?

  • Support it by clicking the ⭐? button on the upper right of this page. ✌?

For full details, visit the documentation on our web site :

Requirements

Fast Android Networking Library can be included in any Android application.

Fast Android Networking Library supports Android 2.3 (Gingerbread) and later.

Using Fast Android Networking Library in your application

Add this in your build.gradle

compile 'com.amitshekhar.android:android-networking:1.0.0'

Do not forget to add internet permission in manifest if already not present

<uses-permission android:name="android.permission.INTERNET" />

Then initialize it in onCreate() Method of application class :

AndroidNetworking.initialize(getApplicationContext());

Initializing it with some customization , as it uses OkHttp as networking layer, you can pass custom okHttpClient while initializing it.

// Adding an Network Interceptor for Debugging purpose : OkHttpClient okHttpClient = new OkHttpClient() .newBuilder()

 .addNetworkInterceptor(new StethoInterceptor())

 .build();
 AndroidNetworking.initialize(getApplicationContext(),okHttpClient);

Using the Fast Android Networking with Jackson Parser

compile 'com.amitshekhar.android:jackson-android-networking:1.0.0'
// Then set the JacksonParserFactory like below AndroidNetworking.setParserFactory(new JacksonParserFactory());

Making a GET Request

AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{
pageNumber
}
")

.addPathParameter("pageNumber", "0")

.addQueryParameter("limit", "3")

.addHeaders("token", "1234")

.setTag("test")

.setPriority(Priority.LOW)

.build()

.getAsJSONArray(new JSONArrayRequestListener() {

@Override

public void onResponse(JSONArray response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

 

Making a POST Request

AndroidNetworking.post("https://fierce-cove-29863.herokuapp.com/createAnUser")

.addBodyParameter("firstname", "Amit")

.addBodyParameter("lastname", "Shekhar")

.setTag("test")

.setPriority(Priority.MEDIUM)

.build()

.getAsJSONObject(new JSONObjectRequestListener() {

@Override

public void onResponse(JSONObject response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

You can also post java object, json, file, etc in POST request like this.

User user = new User();
 user.firstname = "Amit"; user.lastname = "Shekhar";  AndroidNetworking.post("https://fierce-cove-29863.herokuapp.com/createUser")

.addBodyParameter(user) // posting java object

.setTag("test")

.setPriority(Priority.MEDIUM)

.build()

.getAsJSONArray(new JSONArrayRequestListener() {

@Override

public void onResponse(JSONArray response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

JSONObject jsonObject = new JSONObject();
 try {

  jsonObject.put("firstname", "Amit");

  jsonObject.put("lastname", "Shekhar");
 
}
 catch (JSONException e) {

e.printStackTrace();
 
}

AndroidNetworking.post("https://fierce-cove-29863.herokuapp.com/createUser")

.addJSONObjectBody(jsonObject) // posting json

.setTag("test")

.setPriority(Priority.MEDIUM)

.build()

.getAsJSONArray(new JSONArrayRequestListener() {

@Override

public void onResponse(JSONArray response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

AndroidNetworking.post("https://fierce-cove-29863.herokuapp.com/postFile")

.addFileBody(file) // posting any type of file

.setTag("test")

.setPriority(Priority.MEDIUM)

.build()

.getAsJSONObject(new JSONObjectRequestListener() {

@Override

public void onResponse(JSONObject response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

Using it with your own JAVA Object - JSON Parser

/*--------------Example One -> Getting the userList----------------*/ AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{
pageNumber
}
")

  .addPathParameter("pageNumber", "0")

  .addQueryParameter("limit", "3")

  .setTag(this)

  .setPriority(Priority.LOW)

  .build()

  .getAsObjectList(User.class, new ParsedRequestListener<List<User>>() {

@Override

public void onResponse(List<User> users) {

  // do anything with response

  Log.d(TAG, "userList size : " + users.size());

  for (User user : users) {

 Log.d(TAG, "id : " + user.id);

 Log.d(TAG, "firstname : " + user.firstname);

 Log.d(TAG, "lastname : " + user.lastname);

  
}

}

@Override

public void onError(ANError anError) {

 // handle error

}

  
}
);
 /*--------------Example Two -> Getting an user----------------*/ AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAnUserDetail/{
userId
}
")

  .addPathParameter("userId", "1")

  .setTag(this)

  .setPriority(Priority.LOW)

  .build()

  .getAsObject(User.class, new ParsedRequestListener<User>() {

 @Override

 public void onResponse(User user) {

 // do anything with response

 Log.d(TAG, "id : " + user.id);

 Log.d(TAG, "firstname : " + user.firstname);

 Log.d(TAG, "lastname : " + user.lastname);

 
}

 @Override

 public void onError(ANError anError) {

 // handle error

 
}

}
);
  /*-- Note : YourObject.class, getAsObject and getAsObjectList are important here --*/

  

Downloading a file from server

AndroidNetworking.download(url,dirPath,fileName)

.setTag("downloadTest")

.setPriority(Priority.MEDIUM)

.build()

.setDownloadProgressListener(new DownloadProgressListener() {

@Override

public void onProgress(long bytesDownloaded, long totalBytes) {

  // do anything with progress  

}

}
)

.startDownload(new DownloadListener() {

@Override

public void onDownloadComplete() {

  // do anything after completion

}

@Override

public void onError(ANError error) {

  // handle error
 

}

  
}
);

  

Uploading a file to server

AndroidNetworking.upload(url)

.addMultipartFile("image",file)

 .addMultipartParameter("key","value")

.setTag("uploadTest")

.setPriority(Priority.HIGH)

.build()

.setUploadProgressListener(new UploadProgressListener() {

@Override

public void onProgress(long bytesUploaded, long totalBytes) {

  // do anything with progress 

}

}
)

.getAsJSONObject(new JSONObjectRequestListener() {

@Override

public void onResponse(JSONObject response) {

  // do anything with response

 

}

@Override

public void onError(ANError error) {

  // handle error 

}

}
);
 

Getting Response and completion in an another thread executor

(Note : Error and Progress will always be returned in main thread of application)

AndroidNetworking.upload(url)

.addMultipartFile("image",file)

  .addMultipartParameter("key","value")

  .setTag("uploadTest")

.setPriority(Priority.HIGH)

.build()

.setExecutor(Executors.newSingleThreadExecutor()) // setting an executor to get response or completion on that executor thread

.setUploadProgressListener(new UploadProgressListener() {

@Override

public void onProgress(long bytesUploaded, long totalBytes) {

  // do anything with progress 

}

}
)

.getAsJSONObject(new JSONObjectRequestListener() {

@Override

public void onResponse(JSONObject response) {

  // below code will be executed in the executor provided

  // do anything with response

 

}

@Override

public void onError(ANError error) {

  // handle error 

}

}
);
 

Setting a Percentage Threshold For Not Cancelling the request if it has completed the given threshold

AndroidNetworking.download(url,dirPath,fileName)

.setTag("downloadTest")

.setPriority(Priority.MEDIUM)

.setPercentageThresholdForCancelling(50) // even if at the time of cancelling it will not cancel if 50% 

.build()

// downloading is done.But can be cancalled with forceCancel.

.setDownloadProgressListener(new DownloadProgressListener() {

@Override

public void onProgress(long bytesDownloaded, long totalBytes) {

  // do anything with progress  

}

}
)

.startDownload(new DownloadListener() {

@Override

public void onDownloadComplete() {

  // do anything after completion

}

@Override

public void onError(ANError error) {

  // handle error
 

}

  
}
);

Cancelling a request.

Any request with a given tag can be cancelled. Just do like this.

AndroidNetworking.cancel("tag");
 // All the requests with the given tag will be cancelled. AndroidNetworking.forceCancel("tag");
  // All the requests with the given tag will be cancelled , even if any percent threshold is

 // set , it will be cancelled forcefully.  AndroidNetworking.cancelAll();
 // All the requests will be cancelled.   AndroidNetworking.forceCancelAll();
 // All the requests will be cancelled , even if any percent threshold is

  // set , it will be cancelled forcefully.

Loading image from network into ImageView


<com.androidnetworking.widget.ANImageView

  android:id="@+id/imageView"

  android:layout_width="100dp"

  android:layout_height="100dp"

  android:layout_gravity="center" />

imageView.setDefaultImageResId(R.drawable.default);

 imageView.setErrorImageResId(R.drawable.error);

 imageView.setImageUrl(imageUrl);

 

Getting Bitmap from url with some specified parameters

AndroidNetworking.get(imageUrl)

.setTag("imageRequestTag")

.setPriority(Priority.MEDIUM)

.setBitmapMaxHeight(100)

.setBitmapMaxWidth(100)

.setBitmapConfig(Bitmap.Config.ARGB_8888)

.build()

.getAsBitmap(new BitmapRequestListener() {

@Override

public void onResponse(Bitmap bitmap) {

// do anything with bitmap

}

@Override

public void onError(ANError error) {

  // handle error

}

  
}
);

Error Code Handling

public void onError(ANError error) {

 if (error.getErrorCode() != 0) {

// received error from server

// error.getErrorCode() - the error code from server

// error.getErrorBody() - the error body from server

// error.getErrorDetail() - just an error detail

Log.d(TAG, "onError errorCode : " + error.getErrorCode());

Log.d(TAG, "onError errorBody : " + error.getErrorBody());

Log.d(TAG, "onError errorDetail : " + error.getErrorDetail());

// get parsed error object (If ApiError is your class)

ApiError apiError = error.getErrorAsObject(ApiError.class);

 
}
 else {

// error.getErrorDetail() : connectionError, parseError, requestCancelledError

Log.d(TAG, "onError errorDetail : " + error.getErrorDetail());

 
}
 
}

Remove Bitmap from cache or clear cache

AndroidNetworking.evictBitmap(key);
 // remove a bitmap with key from LruCache AndroidNetworking.evictAllBitmap();
 // clear LruCache

Prefetch a request (so that it can return from cache when required at instant)

AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{
pageNumber
}
")

  .addPathParameter("pageNumber", "0")

  .addQueryParameter("limit", "30")

  .setTag(this)

  .setPriority(Priority.LOW)

  .build()

  .prefetch();

Customizing OkHttpClient for a particular request

OkHttpClient okHttpClient = new OkHttpClient().newBuilder()

  .addInterceptor(new GzipRequestInterceptor())

  .build();

AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{
pageNumber
}
")

.addPathParameter("pageNumber", "0")

.addQueryParameter("limit", "3")

.addHeaders("token", "1234")

.setTag("test")

.setPriority(Priority.LOW)

.setOkHttpClient(okHttpClient) // passing a custom okHttpClient 

.build()

.getAsJSONArray(new JSONArrayRequestListener() {

@Override

public void onResponse(JSONArray response) {

  // do anything with response

}

@Override

public void onError(ANError error) {

// handle error

}

  
}
);

Making a conditional request (Building a request)

ANRequest.GetRequestBuilder getRequestBuilder = new ANRequest.GetRequestBuilder(ApiEndPoint.BASE_URL + ApiEndPoint.CHECK_FOR_HEADER);

  if(isHeaderRequired){
  getRequestBuilder.addHeaders("token", "1234");
 
}
  if(executorRequired){
  getRequestBuilder.setExecutor(Executors.newSingleThreadExecutor());
 
}

  ANRequest anRequest = getRequestBuilder.build();

  anRequest.getAsJSONObject(new JSONObjectRequestListener() {

  @Override
  public void onResponse(JSONObject response) {

 // do anything with response
  
}

  @Override
  public void onError(ANError error) {

 // handle error
  
}
 
}
);

ConnectionClass Listener to get current network quality and bandwidth

// Adding Listener AndroidNetworking.setConnectionQualityChangeListener(new ConnectionQualityChangeListener() {

 @Override

 public void onChange(ConnectionQuality currentConnectionQuality, int currentBandwidth) {

// do something on change in connectionQuality

 
}

}
);

 // Removing Listener
 AndroidNetworking.removeConnectionQualityChangeListener();
  // Getting current ConnectionQuality ConnectionQuality connectionQuality = AndroidNetworking.getCurrentConnectionQuality();
 if(connectionQuality == ConnectionQuality.EXCELLENT) {

// do something 
}
 else if (connectionQuality == ConnectionQuality.POOR) {

// do something 
}
 else if (connectionQuality == ConnectionQuality.UNKNOWN) {

// do something 
}
 // Getting current bandwidth int currentBandwidth = AndroidNetworking.getCurrentBandwidth();
 // Note : if (currentBandwidth == 0) : means UNKNOWN

Getting Analytics of a request by setting AnalyticsListener on that

AndroidNetworking.download(url,dirPath,fileName)

.setTag("downloadTest")

.setPriority(Priority.MEDIUM)

.build()

.setAnalyticsListener(new AnalyticsListener() {

  @Override

  public void onReceived(long timeTakenInMillis, long bytesSent, long bytesReceived, boolean isFromCache) {

Log.d(TAG, " timeTakenInMillis : " + timeTakenInMillis);

Log.d(TAG, " bytesSent : " + bytesSent);

Log.d(TAG, " bytesReceived : " + bytesReceived);

Log.d(TAG, " isFromCache : " + isFromCache);

  
}

 
}
)

.setDownloadProgressListener(new DownloadProgressListener() {

@Override

public void onProgress(long bytesDownloaded, long totalBytes) {

  // do anything with progress  

}

}
)

.startDownload(new DownloadListener() {

@Override

public void onDownloadComplete() {

  // do anything after completion

}

@Override

public void onError(ANError error) {

  // handle error
 

}

  
}
);

Note : If bytesSent or bytesReceived is -1 , it means it is unknown

 

Getting OkHttpResponse in Response

AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAnUserDetail/{
userId
}
")

  .addPathParameter("userId", "1")

  .setTag(this)

  .setPriority(Priority.LOW)

  .setUserAgent("getAnUser")

  .build()

  .getAsOkHttpResponseAndParsed(new TypeToken<User>() {

  
}
, new OkHttpResponseAndParsedRequestListener<User>() {

@Override

public void onResponse(Response okHttpResponse, User user) {

  // do anything with okHttpResponse and user

}

@Override

public void onError(ANError anError) {

  // handle error

}

  
}
);

Making Synchronous Request

ANRequest request = AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{
pageNumber
}
")

 .addPathParameter("pageNumber", "0")

 .addQueryParameter("limit", "3")

 .build();
 ANResponse<List<User>> response = request.executeForObjectList(User.class);
 if (response.isSuccess()) {

 List<User> users = responseTwo.getResult();
 
}
 else {

 //handle error 
}

 

How caching works ?

  • First of all the server must send cache-control in header so that is starts working.
  • Response will be cached on the basis of cache-control max-age,max-stale.
  • If internet is connected and the age is NOT expired it will return from cache.
  • If internet is connected and the age is expired and if server returns 304(NOT MODIFIED) it will return from cache.
  • If internet is NOT connected if you are using getResponseOnlyIfCached() - it will return from cache even it date is expired.
  • If internet is NOT connected , if you are NOT using getResponseOnlyIfCached() - it will NOT return anything.
  • If you are using getResponseOnlyFromNetwork() , it will only return response after validation from server.
  • If cache-control is set, it will work according to the max-age,max-stale returned from server.
  • If internet is NOT connected only way to get cache Response is by using getResponseOnlyIfCached().

Enabling Logging

AndroidNetworking.enableLogging();
 // simply enable logging AndroidNetworking.enableLogging(LEVEL.HEADERS);
 // enabling logging with level

Enabling GZIP From Client to Server

// Enabling GZIP for Request (Not needed if your server doesn't support GZIP Compression), anyway responses  // from server are automatically unGzipped if required. So enable it only if you need your request to be  // Gzipped before sending to server(Make sure your server support GZIP Compression). OkHttpClient okHttpClient = new OkHttpClient().newBuilder()

  .addInterceptor(new GzipRequestInterceptor())

  .build();
 AndroidNetworking.initialize(getApplicationContext(),okHttpClient);

 

IMPORTANT NOTE

  • Use IMMEDIATE Priority with caution - use is at appropriate place only when 1 or 2 (at max 2)IMMEDIATE request is required at instant.Otherwise use HIGH Priority.

  • Known Bug : As present if you are using GZIP Interceptor from client to server, Upload progress is not working perfectly in Multipart.

    If you are using Proguard with Gradle build system (which is usually the case), you don't have to do anything. The appropriate Proguard rules will be automatically applied. If you still need the rules applied in proguard-rules.pro, it is as follows:

    -dontwarn okio.** 

Fast Android Networking Library supports

  • Fast Android Networking Library supports all types of HTTP/HTTPS request like GET, POST, DELETE, HEAD, PUT, PATCH
  • Fast Android Networking Library supports downloading any type of file
  • Fast Android Networking Library supports uploading any type of file (supports multipart upload)
  • Fast Android Networking Library supports cancelling a request
  • Fast Android Networking Library supports setting priority to any request (LOW, MEDIUM, HIGH, IMMEDIATE)
  • Fast Android Networking Library supports RxJava

As it uses OkHttp as a networking layer, it supports:

  • Fast Android Networking Library supports HTTP/2 support allows all requests to the same host to share a socket
  • Fast Android Networking Library uses connection pooling which reduces request latency (if HTTP/2 isn’t available)
  • Transparent GZIP shrinks download sizes
  • Fast Android Networking Library supports response caching which avoids the network completely for repeat requests

Difference over other Networking Library

  • In Fast Android Networking Library, OkHttpClient can be customized for every request easily?—?like timeout customization, etc. for each request.
  • As Fast Android Networking Library uses OkHttp and Okio, it is faster.
  • Single library for all type of networking.
  • Supports RxJava, RxJava2 -> Check here
  • Current bandwidth and connection quality can be obtained to decide logic of code.
  • Executor can be passed to any request to get response in another thread.
  • Complete analytics of any request can be obtained.
  • All types of customization is possible.
  • Immediate Request really is immediate now.
  • Prefetching of any request can be done so that it gives instant data when required from the cache.
  • Proper request canceling.
  • Prevents cancellation of a request if it’s completed more than a specific threshold percentage.
  • A simple interface to make any type of request.
  • Proper Response Caching?—?which leads to reduced bandwidth usage.

TODO

  • Integration with other library
  • And of course many many features and bug fixes

CREDITS

Check out Mindorks awesome open source projects here

Contact - Let's become friend

License


Copyright (C) 2016 Amit Shekhar
 Copyright (C) 2011 Android Open Source Project
  Licensed under the Apache License, Version 2.0 (the "License");

 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License. 

Contributing to Fast Android Networking

Just make

Resources

Just another logging framework. Multiple combinable logging classes. Combine local logging with rolling file logging with Crashlytics with NewRelic...

Android UI Automator to JUnit Format Converter.

Reads Android UI automator file outout and write a JUNIT Xml file. For example for usage in CI server as Jenkins.

Write less code! Don't inflate views, menus, preferences manually anymore!

A software analytics tool suite used by developers, ops, and software companies to understand how your applications are performing in development and production.

The main Gravatar-android feature is a simple and fluid API. It fully supports Gravatar image requests as described in the official documentation and automatically computes the request URL.

Lightweight javac @Warning annotation.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes