Improved error handling for Android and iOS. (#775)

Updated FireAuth docs.
Updated deprecated API "fetchProvidersForEmail" to "fetchSignInMethodsForEmail".
Added "unlinkCredential".
Updated changelog and brought wrapper inline with (#915).
diff --git a/packages/firebase_auth/CHANGELOG.md b/packages/firebase_auth/CHANGELOG.md
index 104de53..d0aa9da 100644
--- a/packages/firebase_auth/CHANGELOG.md
+++ b/packages/firebase_auth/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 0.6.7
+
+* `FirebaseAuth` and `FirebaseUser` are now fully documented.
+* `PlatformExceptions` now report error codes as stated in docs.
+* Credentials can now be unlinked from Accounts with new methods on `FirebaseUser`.
+
+## 0.6.6
+
+* Users can now reauthenticate in response to operations that require a recent sign-in.
+
 ## 0.6.5
 
 * Fixing async method `verifyPhoneNumber`, that would never return even in a successful call.
diff --git a/packages/firebase_auth/android/build.gradle b/packages/firebase_auth/android/build.gradle
index 21495d4..7f96901 100755
--- a/packages/firebase_auth/android/build.gradle
+++ b/packages/firebase_auth/android/build.gradle
@@ -32,6 +32,6 @@
         disable 'InvalidPackage'
     }
     dependencies {
-        api 'com.google.firebase:firebase-auth:16.0.4'
+        api 'com.google.firebase:firebase-auth:16.0.5'
     }
 }
diff --git a/packages/firebase_auth/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_auth/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..019065d
--- /dev/null
+++ b/packages/firebase_auth/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/packages/firebase_auth/android/src/main/java/io/flutter/plugins/firebaseauth/FirebaseAuthPlugin.java b/packages/firebase_auth/android/src/main/java/io/flutter/plugins/firebaseauth/FirebaseAuthPlugin.java
index a3a68de..4bf1ce2 100755
--- a/packages/firebase_auth/android/src/main/java/io/flutter/plugins/firebaseauth/FirebaseAuthPlugin.java
+++ b/packages/firebase_auth/android/src/main/java/io/flutter/plugins/firebaseauth/FirebaseAuthPlugin.java
@@ -6,14 +6,34 @@
 
 import android.net.Uri;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.SparseArray;
 import com.google.android.gms.tasks.OnCompleteListener;
 import com.google.android.gms.tasks.Task;
 import com.google.firebase.FirebaseApiNotAvailableException;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseException;
+import com.google.firebase.FirebaseNetworkException;
 import com.google.firebase.FirebaseTooManyRequestsException;
-import com.google.firebase.auth.*;
+import com.google.firebase.auth.AuthCredential;
+import com.google.firebase.auth.AuthResult;
+import com.google.firebase.auth.EmailAuthProvider;
+import com.google.firebase.auth.FacebookAuthProvider;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseAuth.AuthStateListener;
+import com.google.firebase.auth.FirebaseAuthException;
+import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.auth.GetTokenResult;
+import com.google.firebase.auth.GithubAuthProvider;
+import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.PhoneAuthCredential;
+import com.google.firebase.auth.PhoneAuthProvider;
+import com.google.firebase.auth.PhoneAuthProvider.ForceResendingToken;
+import com.google.firebase.auth.SignInMethodQueryResult;
+import com.google.firebase.auth.TwitterAuthProvider;
+import com.google.firebase.auth.UserInfo;
+import com.google.firebase.auth.UserProfileChangeRequest;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -23,23 +43,20 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /** Flutter plugin for Firebase Auth. */
 public class FirebaseAuthPlugin implements MethodCallHandler {
   private final PluginRegistry.Registrar registrar;
-  private final SparseArray<FirebaseAuth.AuthStateListener> authStateListeners =
-      new SparseArray<>();
-  private final SparseArray<PhoneAuthProvider.ForceResendingToken> forceResendingTokens =
-      new SparseArray<>();
+  private final SparseArray<AuthStateListener> authStateListeners = new SparseArray<>();
+  private final SparseArray<ForceResendingToken> forceResendingTokens = new SparseArray<>();
   private final MethodChannel channel;
 
   // Handles are ints used as indexes into the sparse array of active observers
   private int nextHandle = 0;
 
-  private static final String ERROR_REASON_EXCEPTION = "exception";
-
   public static void registerWith(PluginRegistry.Registrar registrar) {
     MethodChannel channel =
         new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_auth");
@@ -53,7 +70,7 @@
   }
 
   private FirebaseAuth getAuth(MethodCall call) {
-    Map<String, Object> arguments = (Map<String, Object>) call.arguments;
+    Map<String, Object> arguments = call.arguments();
     String appName = (String) arguments.get("app");
     FirebaseApp app = FirebaseApp.getInstance(appName);
     return FirebaseAuth.getInstance(app);
@@ -71,8 +88,8 @@
       case "createUserWithEmailAndPassword":
         handleCreateUserWithEmailAndPassword(call, result, getAuth(call));
         break;
-      case "fetchProvidersForEmail":
-        handleFetchProvidersForEmail(call, result, getAuth(call));
+      case "fetchSignInMethodsForEmail":
+        handleFetchSignInMethodsForEmail(call, result, getAuth(call));
         break;
       case "sendPasswordResetEmail":
         handleSendPasswordResetEmail(call, result, getAuth(call));
@@ -140,6 +157,9 @@
       case "linkWithGithubCredential":
         handleLinkWithGithubCredential(call, result, getAuth(call));
         break;
+      case "unlinkCredential":
+        handleUnlinkCredential(call, result, getAuth(call));
+        break;
       case "updateEmail":
         handleUpdateEmail(call, result, getAuth(call));
         break;
@@ -172,8 +192,7 @@
 
   private void handleSignInWithPhoneNumber(
       MethodCall call, Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String verificationId = arguments.get("verificationId");
     String smsCode = arguments.get("smsCode");
 
@@ -186,10 +205,10 @@
 
   private void handleVerifyPhoneNumber(
       MethodCall call, Result result, final FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    final int handle = call.argument("handle");
-    String phoneNumber = call.argument("phoneNumber");
-    int timeout = call.argument("timeout");
+    Map<String, Object> arguments = call.arguments();
+    final int handle = (int) arguments.get("handle");
+    String phoneNumber = (String) arguments.get("phoneNumber");
+    int timeout = (int) arguments.get("timeout");
 
     PhoneAuthProvider.OnVerificationStateChangedCallbacks verificationCallbacks =
         new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@@ -238,7 +257,7 @@
         };
 
     if (call.argument("forceResendingToken") != null) {
-      int forceResendingTokenKey = call.argument("forceResendingToken");
+      int forceResendingTokenKey = (int) arguments.get("forceResendingToken");
       PhoneAuthProvider.ForceResendingToken forceResendingToken =
           forceResendingTokens.get(forceResendingTokenKey);
       PhoneAuthProvider.getInstance()
@@ -263,9 +282,7 @@
   }
 
   private Map<String, Object> getVerifyPhoneNumberExceptionMap(FirebaseException e) {
-    Map<String, Object> exceptionMap = new HashMap<>();
     String errorCode = "verifyPhoneNumberError";
-
     if (e instanceof FirebaseAuthInvalidCredentialsException) {
       errorCode = "invalidCredential";
     } else if (e instanceof FirebaseAuthException) {
@@ -275,6 +292,8 @@
     } else if (e instanceof FirebaseApiNotAvailableException) {
       errorCode = "apiNotAvailable";
     }
+
+    Map<String, Object> exceptionMap = new HashMap<>();
     exceptionMap.put("code", errorCode);
     exceptionMap.put("message", e.getMessage());
     return exceptionMap;
@@ -282,8 +301,7 @@
 
   private void handleLinkWithEmailAndPassword(
       MethodCall call, Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
     String password = arguments.get("password");
 
@@ -294,7 +312,8 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
-  private void handleCurrentUser(MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+  private void handleCurrentUser(
+      @SuppressWarnings("unused") MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
     FirebaseUser user = firebaseAuth.getCurrentUser();
     if (user == null) {
       result.success(null);
@@ -305,14 +324,13 @@
   }
 
   private void handleSignInAnonymously(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+      @SuppressWarnings("unused") MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     firebaseAuth.signInAnonymously().addOnCompleteListener(new SignInCompleteListener(result));
   }
 
   private void handleCreateUserWithEmailAndPassword(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
     String password = arguments.get("password");
 
@@ -321,21 +339,19 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
-  private void handleFetchProvidersForEmail(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleFetchSignInMethodsForEmail(
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
 
     firebaseAuth
-        .fetchProvidersForEmail(email)
-        .addOnCompleteListener(new ProvidersCompleteListener(result));
+        .fetchSignInMethodsForEmail(email)
+        .addOnCompleteListener(new GetSignInMethodsCompleteListener(result));
   }
 
   private void handleSendPasswordResetEmail(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
 
     firebaseAuth
@@ -344,24 +360,30 @@
   }
 
   private void handleSendEmailVerification(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+      @SuppressWarnings("unused") MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     firebaseAuth
         .getCurrentUser()
         .sendEmailVerification()
         .addOnCompleteListener(new TaskVoidCompleteListener(result));
   }
 
-  private void handleReload(MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+  private void handleReload(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     firebaseAuth
         .getCurrentUser()
         .reload()
         .addOnCompleteListener(new TaskVoidCompleteListener(result));
   }
 
+  private void handleDelete(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    firebaseAuth
+        .getCurrentUser()
+        .delete()
+        .addOnCompleteListener(new TaskVoidCompleteListener(result));
+  }
+
   private void handleSignInWithEmailAndPassword(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
     String password = arguments.get("password");
 
@@ -370,19 +392,11 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
-  private void handleDelete(MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    firebaseAuth
-        .getCurrentUser()
-        .delete()
-        .addOnCompleteListener(new TaskVoidCompleteListener(result));
-  }
-
-  private void handleSignInWithGoogle(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleSignInWithGoogle(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String idToken = arguments.get("idToken");
     String accessToken = arguments.get("accessToken");
+
     AuthCredential credential = GoogleAuthProvider.getCredential(idToken, accessToken);
     firebaseAuth
         .signInWithCredential(credential)
@@ -391,8 +405,7 @@
 
   private void handleReauthenticateWithEmailAndPassword(
       MethodCall call, Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String email = arguments.get("email");
     String password = arguments.get("password");
 
@@ -405,10 +418,10 @@
 
   private void handleReauthenticateWithGoogleCredential(
       MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String idToken = arguments.get("idToken");
     String accessToken = arguments.get("accessToken");
+
     AuthCredential credential = GoogleAuthProvider.getCredential(idToken, accessToken);
     firebaseAuth
         .getCurrentUser()
@@ -418,9 +431,9 @@
 
   private void handleReauthenticateWithFacebookCredential(
       MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String accessToken = arguments.get("accessToken");
+
     AuthCredential credential = FacebookAuthProvider.getCredential(accessToken);
     firebaseAuth
         .getCurrentUser()
@@ -430,10 +443,10 @@
 
   private void handleReauthenticateWithTwitterCredential(
       MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+    Map<String, String> arguments = call.arguments();
     String authToken = arguments.get("authToken");
     String authTokenSecret = arguments.get("authTokenSecret");
+
     AuthCredential credential = TwitterAuthProvider.getCredential(authToken, authTokenSecret);
     firebaseAuth
         .getCurrentUser()
@@ -444,6 +457,7 @@
   private void handleReauthenticateWithGithubCredential(
       MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
     String token = call.argument("token");
+
     AuthCredential credential = GithubAuthProvider.getCredential(token);
     firebaseAuth
         .getCurrentUser()
@@ -452,11 +466,11 @@
   }
 
   private void handleLinkWithGoogleCredential(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String idToken = arguments.get("idToken");
     String accessToken = arguments.get("accessToken");
+
     AuthCredential credential = GoogleAuthProvider.getCredential(idToken, accessToken);
     firebaseAuth
         .getCurrentUser()
@@ -465,10 +479,10 @@
   }
 
   private void handleLinkWithFacebookCredential(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String accessToken = arguments.get("accessToken");
+
     AuthCredential credential = FacebookAuthProvider.getCredential(accessToken);
     firebaseAuth
         .getCurrentUser()
@@ -476,12 +490,31 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
+  private void handleSignInWithFacebook(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
+    String accessToken = arguments.get("accessToken");
+
+    AuthCredential credential = FacebookAuthProvider.getCredential(accessToken);
+    firebaseAuth
+        .signInWithCredential(credential)
+        .addOnCompleteListener(new SignInCompleteListener(result));
+  }
+
+  private void handleSignInWithTwitter(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    String authToken = call.argument("authToken");
+    String authTokenSecret = call.argument("authTokenSecret");
+
+    AuthCredential credential = TwitterAuthProvider.getCredential(authToken, authTokenSecret);
+    firebaseAuth
+        .signInWithCredential(credential)
+        .addOnCompleteListener(new SignInCompleteListener(result));
+  }
+
   private void handleLinkWithTwitterCredential(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
-    String authToken = arguments.get("authToken");
-    String authTokenSecret = arguments.get("authTokenSecret");
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    String authToken = call.argument("authToken");
+    String authTokenSecret = call.argument("authTokenSecret");
+
     AuthCredential credential = TwitterAuthProvider.getCredential(authToken, authTokenSecret);
     firebaseAuth
         .getCurrentUser()
@@ -489,9 +522,19 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
+  private void handleSignInWithGithub(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    String token = call.argument("token");
+
+    AuthCredential credential = GithubAuthProvider.getCredential(token);
+    firebaseAuth
+        .signInWithCredential(credential)
+        .addOnCompleteListener(new SignInCompleteListener(result));
+  }
+
   private void handleLinkWithGithubCredential(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     String token = call.argument("token");
+
     AuthCredential credential = GithubAuthProvider.getCredential(token);
     firebaseAuth
         .getCurrentUser()
@@ -499,33 +542,13 @@
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
-  private void handleSignInWithFacebook(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
-    String accessToken = arguments.get("accessToken");
-    AuthCredential credential = FacebookAuthProvider.getCredential(accessToken);
-    firebaseAuth
-        .signInWithCredential(credential)
-        .addOnCompleteListener(new SignInCompleteListener(result));
-  }
+  private void handleUnlinkCredential(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
+    final String provider = arguments.get("provider");
 
-  private void handleSignInWithTwitter(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    String authToken = call.argument("authToken");
-    String authTokenSecret = call.argument("authTokenSecret");
-    AuthCredential credential = TwitterAuthProvider.getCredential(authToken, authTokenSecret);
     firebaseAuth
-        .signInWithCredential(credential)
-        .addOnCompleteListener(new SignInCompleteListener(result));
-  }
-
-  private void handleSignInWithGithub(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    String token = call.argument("token");
-    AuthCredential credential = GithubAuthProvider.getCredential(token);
-    firebaseAuth
-        .signInWithCredential(credential)
+        .getCurrentUser()
+        .unlink(provider)
         .addOnCompleteListener(new SignInCompleteListener(result));
   }
 
@@ -533,6 +556,7 @@
       MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
     Map<String, String> arguments = call.arguments();
     String token = arguments.get("token");
+
     firebaseAuth
         .signInWithCustomToken(token)
         .addOnCompleteListener(new SignInCompleteListener(result));
@@ -544,9 +568,9 @@
   }
 
   private void handleGetToken(MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, Boolean> arguments = (Map<String, Boolean>) call.arguments;
+    Map<String, Boolean> arguments = call.arguments();
     boolean refresh = arguments.get("refresh");
+
     firebaseAuth
         .getCurrentUser()
         .getIdToken(refresh)
@@ -557,35 +581,34 @@
                   String idToken = task.getResult().getToken();
                   result.success(idToken);
                 } else {
-                  result.error(ERROR_REASON_EXCEPTION, task.getException().getMessage(), null);
+                  reportException(result, task.getException());
                 }
               }
             });
   }
 
-  private void handleUpdateEmail(MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleUpdateEmail(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
+    final String email = arguments.get("email");
+
     firebaseAuth
         .getCurrentUser()
-        .updateEmail(arguments.get("email"))
+        .updateEmail(email)
         .addOnCompleteListener(new TaskVoidCompleteListener(result));
   }
 
-  private void handleUpdatePassword(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleUpdatePassword(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
+    final String password = arguments.get("password");
+
     firebaseAuth
         .getCurrentUser()
-        .updatePassword(arguments.get("password"))
+        .updatePassword(password)
         .addOnCompleteListener(new TaskVoidCompleteListener(result));
   }
 
-  private void handleUpdateProfile(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleUpdateProfile(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
 
     UserProfileChangeRequest.Builder builder = new UserProfileChangeRequest.Builder();
     if (arguments.containsKey("displayName")) {
@@ -602,7 +625,7 @@
   }
 
   private void handleStartListeningAuthState(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+      @SuppressWarnings("unused") MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     final int handle = nextHandle++;
     FirebaseAuth.AuthStateListener listener =
         new FirebaseAuth.AuthStateListener() {
@@ -612,7 +635,6 @@
             Map<String, Object> userMap = mapFromUser(user);
             Map<String, Object> map = new HashMap<>();
             map.put("id", handle);
-
             if (userMap != null) {
               map.put("user", userMap);
             }
@@ -625,7 +647,7 @@
   }
 
   private void handleStopListeningAuthState(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
+      MethodCall call, Result result, FirebaseAuth firebaseAuth) {
     Map<String, Integer> arguments = call.arguments();
     Integer id = arguments.get("id");
 
@@ -635,17 +657,16 @@
       authStateListeners.remove(id);
       result.success(null);
     } else {
-      result.error(
-          ERROR_REASON_EXCEPTION,
-          String.format("Listener with identifier '%d' not found.", id),
-          null);
+      reportException(
+          result,
+          new FirebaseAuthException(
+              "ERROR_LISTENER_NOT_FOUND",
+              String.format(Locale.US, "Listener with identifier '%d' not found.", id)));
     }
   }
 
-  private void handleSetLanguageCode(
-      MethodCall call, final Result result, FirebaseAuth firebaseAuth) {
-    @SuppressWarnings("unchecked")
-    Map<String, String> arguments = (Map<String, String>) call.arguments;
+  private void handleSetLanguageCode(MethodCall call, Result result, FirebaseAuth firebaseAuth) {
+    Map<String, String> arguments = call.arguments();
     String language = arguments.get("language");
 
     firebaseAuth.setLanguageCode(language);
@@ -661,9 +682,8 @@
 
     @Override
     public void onComplete(@NonNull Task<AuthResult> task) {
-      if (!task.isSuccessful()) {
-        Exception e = task.getException();
-        result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
+      if (!task.isSuccessful() || task.getResult() == null) {
+        reportException(result, task.getException());
       } else {
         FirebaseUser user = task.getResult().getUser();
         Map<String, Object> userMap = Collections.unmodifiableMap(mapFromUser(user));
@@ -682,28 +702,27 @@
     @Override
     public void onComplete(@NonNull Task<Void> task) {
       if (!task.isSuccessful()) {
-        Exception e = task.getException();
-        result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
+        reportException(result, task.getException());
       } else {
         result.success(null);
       }
     }
   }
 
-  private class ProvidersCompleteListener implements OnCompleteListener<ProviderQueryResult> {
+  private class GetSignInMethodsCompleteListener
+      implements OnCompleteListener<SignInMethodQueryResult> {
     private final Result result;
 
-    ProvidersCompleteListener(Result result) {
+    GetSignInMethodsCompleteListener(Result result) {
       this.result = result;
     }
 
     @Override
-    public void onComplete(@NonNull Task<ProviderQueryResult> task) {
-      if (!task.isSuccessful()) {
-        Exception e = task.getException();
-        result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
+    public void onComplete(@NonNull Task<SignInMethodQueryResult> task) {
+      if (!task.isSuccessful() || task.getResult() == null) {
+        reportException(result, task.getException());
       } else {
-        List<String> providers = task.getResult().getProviders();
+        List<String> providers = task.getResult().getSignInMethods();
         result.success(providers);
       }
     }
@@ -749,4 +768,23 @@
       return null;
     }
   }
+
+  private void reportException(Result result, @Nullable Exception exception) {
+    if (exception != null) {
+      if (exception instanceof FirebaseAuthException) {
+        final FirebaseAuthException authException = (FirebaseAuthException) exception;
+        result.error(authException.getErrorCode(), exception.getMessage(), null);
+      } else if (exception instanceof FirebaseApiNotAvailableException) {
+        result.error("ERROR_API_NOT_AVAILABLE", exception.getMessage(), null);
+      } else if (exception instanceof FirebaseTooManyRequestsException) {
+        result.error("ERROR_TOO_MANY_REQUESTS", exception.getMessage(), null);
+      } else if (exception instanceof FirebaseNetworkException) {
+        result.error("ERROR_NETWORK_REQUEST_FAILED", exception.getMessage(), null);
+      } else {
+        result.error(exception.getClass().getSimpleName(), exception.getMessage(), null);
+      }
+    } else {
+      result.error("ERROR_UNKNOWN", "An unknown error occurred.", null);
+    }
+  }
 }
diff --git a/packages/firebase_auth/ios/Classes/FirebaseAuthPlugin.m b/packages/firebase_auth/ios/Classes/FirebaseAuthPlugin.m
index ebac80f..5bbeaf3 100644
--- a/packages/firebase_auth/ios/Classes/FirebaseAuthPlugin.m
+++ b/packages/firebase_auth/ios/Classes/FirebaseAuthPlugin.m
@@ -6,15 +6,17 @@
 
 #import "Firebase/Firebase.h"
 
-@interface NSError (FlutterError)
-@property(readonly, nonatomic) FlutterError *flutterError;
+@interface NSError (FIRAuthErrorCode)
+@property(readonly, nonatomic) NSString *firAuthErrorCode;
 @end
 
-@implementation NSError (FlutterError)
-- (FlutterError *)flutterError {
-  return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)self.code]
-                             message:self.domain
-                             details:self.localizedDescription];
+@implementation NSError (FIRAuthErrorCode)
+- (NSString *)firAuthErrorCode {
+  NSString *code = [self userInfo][FIRAuthErrorNameKey];
+  if (code != nil) {
+    return code;
+  }
+  return [NSString stringWithFormat:@"ERROR_%d", (int)self.code];
 }
 @end
 
@@ -73,8 +75,8 @@
         }];
   } else if ([@"signInAnonymously" isEqualToString:call.method]) {
     [[self getAuth:call.arguments]
-        signInAnonymouslyWithCompletion:^(FIRAuthDataResult *dataResult, NSError *error) {
-          [self sendResult:result forUser:dataResult.user error:error];
+        signInAnonymouslyWithCompletion:^(FIRAuthDataResult *authResult, NSError *error) {
+          [self sendResult:result forUser:authResult.user error:error];
         }];
   } else if ([@"signInWithGoogle" isEqualToString:call.method]) {
     NSString *idToken = call.arguments[@"idToken"];
@@ -114,35 +116,35 @@
     [[self getAuth:call.arguments]
         createUserWithEmail:email
                    password:password
-                 completion:^(FIRAuthDataResult *dataResult, NSError *error) {
-                   [self sendResult:result forUser:dataResult.user error:error];
+                 completion:^(FIRAuthDataResult *authResult, NSError *error) {
+                   [self sendResult:result forUser:authResult.user error:error];
                  }];
-  } else if ([@"fetchProvidersForEmail" isEqualToString:call.method]) {
+  } else if ([@"fetchSignInMethodsForEmail" isEqualToString:call.method]) {
     NSString *email = call.arguments[@"email"];
     [[self getAuth:call.arguments]
         fetchProvidersForEmail:email
                     completion:^(NSArray<NSString *> *providers, NSError *error) {
-                      [self sendResult:result forProviders:providers error:error];
+                      [self sendResult:result forObject:providers error:error];
                     }];
   } else if ([@"sendEmailVerification" isEqualToString:call.method]) {
     [[self getAuth:call.arguments].currentUser
         sendEmailVerificationWithCompletion:^(NSError *_Nullable error) {
-          [self sendResult:result forProviders:nil error:error];
+          [self sendResult:result forObject:nil error:error];
         }];
   } else if ([@"reload" isEqualToString:call.method]) {
     [[self getAuth:call.arguments].currentUser reloadWithCompletion:^(NSError *_Nullable error) {
-      [self sendResult:result forProviders:nil error:error];
+      [self sendResult:result forObject:nil error:error];
     }];
   } else if ([@"delete" isEqualToString:call.method]) {
     [[self getAuth:call.arguments].currentUser deleteWithCompletion:^(NSError *_Nullable error) {
-      [self sendResult:result forProviders:nil error:error];
+      [self sendResult:result forObject:nil error:error];
     }];
   } else if ([@"sendPasswordResetEmail" isEqualToString:call.method]) {
     NSString *email = call.arguments[@"email"];
     [[self getAuth:call.arguments] sendPasswordResetWithEmail:email
                                                    completion:^(NSError *error) {
                                                      [self sendResult:result
-                                                              forUser:nil
+                                                            forObject:nil
                                                                 error:error];
                                                    }];
   } else if ([@"signInWithEmailAndPassword" isEqualToString:call.method]) {
@@ -151,23 +153,23 @@
     [[self getAuth:call.arguments]
         signInWithEmail:email
                password:password
-             completion:^(FIRAuthDataResult *dataResult, NSError *error) {
-               [self sendResult:result forUser:dataResult.user error:error];
+             completion:^(FIRAuthDataResult *authResult, NSError *error) {
+               [self sendResult:result forUser:authResult.user error:error];
              }];
   } else if ([@"signOut" isEqualToString:call.method]) {
     NSError *signOutError;
     BOOL status = [[self getAuth:call.arguments] signOut:&signOutError];
     if (!status) {
       NSLog(@"Error signing out: %@", signOutError);
-      [self sendResult:result forUser:nil error:signOutError];
+      [self sendResult:result forObject:nil error:signOutError];
     } else {
-      [self sendResult:result forUser:nil error:nil];
+      [self sendResult:result forObject:nil error:nil];
     }
   } else if ([@"getIdToken" isEqualToString:call.method]) {
     [[self getAuth:call.arguments].currentUser
         getIDTokenForcingRefresh:YES
                       completion:^(NSString *_Nullable token, NSError *_Nullable error) {
-                        result(error != nil ? error.flutterError : token);
+                        [self sendResult:result forObject:token error:error];
                       }];
   } else if ([@"reauthenticateWithEmailAndPassword" isEqualToString:call.method]) {
     NSString *email = call.arguments[@"email"];
@@ -177,7 +179,7 @@
     [[self getAuth:call.arguments].currentUser
         reauthenticateWithCredential:credential
                           completion:^(NSError *_Nullable error) {
-                            [self sendResult:result forUser:nil error:error];
+                            [self sendResult:result forObject:nil error:error];
                           }];
   } else if ([@"reauthenticateWithGoogleCredential" isEqualToString:call.method]) {
     NSString *idToken = call.arguments[@"idToken"];
@@ -187,7 +189,7 @@
     [[self getAuth:call.arguments].currentUser
         reauthenticateWithCredential:credential
                           completion:^(NSError *_Nullable error) {
-                            [self sendResult:result forUser:nil error:error];
+                            [self sendResult:result forObject:nil error:error];
                           }];
   } else if ([@"reauthenticateWithFacebookCredential" isEqualToString:call.method]) {
     NSString *accessToken = call.arguments[@"accessToken"];
@@ -195,7 +197,7 @@
     [[self getAuth:call.arguments].currentUser
         reauthenticateWithCredential:credential
                           completion:^(NSError *_Nullable error) {
-                            [self sendResult:result forUser:nil error:error];
+                            [self sendResult:result forObject:nil error:error];
                           }];
   } else if ([@"reauthenticateWithTwitterCredential" isEqualToString:call.method]) {
     NSString *authToken = call.arguments[@"authToken"];
@@ -205,7 +207,7 @@
     [[self getAuth:call.arguments].currentUser
         reauthenticateWithCredential:credential
                           completion:^(NSError *_Nullable error) {
-                            [self sendResult:result forUser:nil error:error];
+                            [self sendResult:result forObject:nil error:error];
                           }];
   } else if ([@"reauthenticateWithGithubCredential" isEqualToString:call.method]) {
     NSString *token = call.arguments[@"token"];
@@ -213,7 +215,7 @@
     [[self getAuth:call.arguments].currentUser
         reauthenticateWithCredential:credential
                           completion:^(NSError *_Nullable error) {
-                            [self sendResult:result forUser:nil error:error];
+                            [self sendResult:result forObject:nil error:error];
                           }];
   } else if ([@"linkWithEmailAndPassword" isEqualToString:call.method]) {
     NSString *email = call.arguments[@"email"];
@@ -260,24 +262,32 @@
   } else if ([@"linkWithGithubCredential" isEqualToString:call.method]) {
     NSString *token = call.arguments[@"token"];
     FIRAuthCredential *credential = [FIRGitHubAuthProvider credentialWithToken:token];
-    [[self getAuth:call.arguments].currentUser linkWithCredential:credential
-                                                       completion:^(FIRUser *user, NSError *error) {
-                                                         [self sendResult:result
-                                                                  forUser:user
-                                                                    error:error];
-                                                       }];
+    [[self getAuth:call.arguments].currentUser
+        linkWithCredential:credential
+                completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+                  [self sendResult:result forUser:user error:error];
+                }];
+  } else if ([@"unlinkCredential" isEqualToString:call.method]) {
+    NSString *provider = call.arguments[@"provider"];
+    [[self getAuth:call.arguments].currentUser
+        unlinkFromProvider:provider
+                completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+                  [self sendResult:result forUser:user error:error];
+                }];
   } else if ([@"updateEmail" isEqualToString:call.method]) {
     NSString *email = call.arguments[@"email"];
     [[self getAuth:call.arguments].currentUser updateEmail:email
                                                 completion:^(NSError *error) {
-                                                  [self sendResult:result forUser:nil error:error];
+                                                  [self sendResult:result
+                                                         forObject:nil
+                                                             error:error];
                                                 }];
   } else if ([@"updatePassword" isEqualToString:call.method]) {
     NSString *password = call.arguments[@"password"];
     [[self getAuth:call.arguments].currentUser updatePassword:password
                                                    completion:^(NSError *error) {
                                                      [self sendResult:result
-                                                              forUser:nil
+                                                            forObject:nil
                                                                 error:error];
                                                    }];
   } else if ([@"updateProfile" isEqualToString:call.method]) {
@@ -290,20 +300,14 @@
       changeRequest.photoURL = [NSURL URLWithString:call.arguments[@"photoUrl"]];
     }
     [changeRequest commitChangesWithCompletion:^(NSError *error) {
-      [self sendResult:result forUser:nil error:error];
+      [self sendResult:result forObject:nil error:error];
     }];
-  } else if ([@"updateEmail" isEqualToString:call.method]) {
-    NSString *toEmail = call.arguments[@"email"];
-    [[self getAuth:call.arguments].currentUser updateEmail:toEmail
-                                                completion:^(NSError *_Nullable error) {
-                                                  [self sendResult:result forUser:nil error:error];
-                                                }];
   } else if ([@"signInWithCustomToken" isEqualToString:call.method]) {
     NSString *token = call.arguments[@"token"];
     [[self getAuth:call.arguments]
         signInWithCustomToken:token
-                   completion:^(FIRAuthDataResult *dataResult, NSError *error) {
-                     [self sendResult:result forUser:dataResult.user error:error];
+                   completion:^(FIRAuthDataResult *authResult, NSError *error) {
+                     [self sendResult:result forUser:authResult.user error:error];
                    }];
 
   } else if ([@"startListeningAuthState" isEqualToString:call.method]) {
@@ -332,7 +336,7 @@
       result(nil);
     } else {
       result([FlutterError
-          errorWithCode:@"not_found"
+          errorWithCode:@"ERROR_LISTENER_NOT_FOUND"
                 message:[NSString stringWithFormat:@"Listener with identifier '%d' not found.",
                                                    identifier.intValue]
                 details:nil]);
@@ -364,14 +368,15 @@
     FIRPhoneAuthCredential *credential =
         [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationId
                                                      verificationCode:smsCode];
-    [[self getAuth:call.arguments] signInWithCredential:credential
-                                             completion:^(FIRUser *user, NSError *error) {
-                                               [self sendResult:result forUser:user error:error];
-                                             }];
+    [[self getAuth:call.arguments]
+        signInWithCredential:credential
+                  completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+                    [self sendResult:result forUser:user error:error];
+                  }];
   } else if ([@"setLanguageCode" isEqualToString:call.method]) {
     NSString *language = call.arguments[@"language"];
     [[self getAuth:call.arguments] setLanguageCode:language];
-    [self sendResult:result forUser:nil error:nil];
+    [self sendResult:result forObject:nil error:nil];
   } else {
     result(FlutterMethodNotImplemented);
   }
@@ -397,24 +402,20 @@
 }
 
 - (void)sendResult:(FlutterResult)result forUser:(FIRUser *)user error:(NSError *)error {
-  if (error != nil) {
-    result(error.flutterError);
-  } else if (user == nil) {
-    result(nil);
-  } else {
-    result([self dictionaryFromUser:user]);
-  }
+  [self sendResult:result
+         forObject:(user != nil ? [self dictionaryFromUser:user] : nil)
+             error:error];
 }
 
-- (void)sendResult:(FlutterResult)result
-      forProviders:(NSArray<NSString *> *)providers
-             error:(NSError *)error {
+- (void)sendResult:(FlutterResult)result forObject:(NSObject *)object error:(NSError *)error {
   if (error != nil) {
-    result(error.flutterError);
-  } else if (providers == nil) {
+    result([FlutterError errorWithCode:error.firAuthErrorCode
+                               message:error.localizedDescription
+                               details:nil]);
+  } else if (object == nil) {
     result(nil);
   } else {
-    result(providers);
+    result(object);
   }
 }
 
diff --git a/packages/firebase_auth/lib/firebase_auth.dart b/packages/firebase_auth/lib/firebase_auth.dart
index 9392695..1c377e5 100755
--- a/packages/firebase_auth/lib/firebase_auth.dart
+++ b/packages/firebase_auth/lib/firebase_auth.dart
@@ -52,6 +52,7 @@
 }
 
 /// Represents user profile data that can be updated by [updateProfile]
+///
 /// The purpose of having separate class with a map is to give possibility
 /// to check if value was set to null or not provided
 class UserUpdateInfo {
@@ -59,13 +60,13 @@
   final Map<String, String> _updateData = <String, String>{};
 
   set displayName(String displayName) =>
-      _updateData["displayName"] = displayName;
+      _updateData['displayName'] = displayName;
 
-  String get displayName => _updateData["displayName"];
+  String get displayName => _updateData['displayName'];
 
-  set photoUrl(String photoUri) => _updateData["photoUrl"] = photoUri;
+  set photoUrl(String photoUri) => _updateData['photoUrl'] = photoUri;
 
-  String get photoUrl => _updateData["photoUrl"];
+  String get photoUrl => _updateData['photoUrl'];
 }
 
 /// Represents a user.
@@ -92,6 +93,10 @@
 
   /// Obtains the id token for the current user, forcing a [refresh] if desired.
   ///
+  /// Useful when authenticating against your own backend. Use our server
+  /// SDKs or follow the official documentation to securely verify the
+  /// integrity and validity of this token.
+  ///
   /// Completes with an error if the user is signed out.
   Future<String> getIdToken({bool refresh = false}) async {
     return await FirebaseAuth.channel
@@ -101,12 +106,14 @@
     });
   }
 
+  /// Initiates email verification for the user.
   Future<void> sendEmailVerification() async {
     await FirebaseAuth.channel.invokeMethod(
         'sendEmailVerification', <String, String>{'app': _app.name});
   }
 
-  /// Manually refreshes the data of the current user (for example, attached providers, display name, and so on).
+  /// Manually refreshes the data of the current user (for example,
+  /// attached providers, display name, and so on).
   Future<void> reload() async {
     await FirebaseAuth.channel
         .invokeMethod('reload', <String, String>{'app': _app.name});
@@ -119,6 +126,21 @@
   }
 
   /// Updates the email address of the user.
+  ///
+  /// The original email address recipient will receive an email that allows
+  /// them to revoke the email address change, in order to protect them
+  /// from account hijacking.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the email address is malformed.
+  ///   • `ERROR_EMAIL_ALREADY_IN_USE` - If the email is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> updateEmail(String email) async {
     assert(email != null);
     return await FirebaseAuth.channel.invokeMethod(
@@ -128,6 +150,19 @@
   }
 
   /// Updates the password of the user.
+  ///
+  /// Anonymous users who update both their email and password will no
+  /// longer be anonymous. They will be able to log in with these credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_WEAK_PASSWORD` - If the password is not strong enough.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> updatePassword(String password) async {
     assert(password != null);
     return await FirebaseAuth.channel.invokeMethod(
@@ -137,6 +172,10 @@
   }
 
   /// Updates the user profile information.
+  ///
+  /// Errors:
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
   Future<void> updateProfile(UserUpdateInfo userUpdateInfo) async {
     assert(userUpdateInfo != null);
     final Map<String, String> data = userUpdateInfo._updateData;
@@ -147,6 +186,96 @@
     );
   }
 
+  /// Detaches Email & Password from this user.
+  ///
+  /// This detaches the Email & Password from the current user. This will
+  /// prevent the user from signing in to this account with those credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_NO_SUCH_PROVIDER` - If the user does not have an Email & Password linked to their account.
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  Future<void> unlinkEmailAndPassword() async {
+    return await FirebaseAuth.channel.invokeMethod(
+      'unlinkCredential',
+      <String, String>{'provider': 'password', 'app': _app.name},
+    );
+  }
+
+  /// Detaches Google from this user.
+  ///
+  /// This detaches the Google Account from the current user. This will
+  /// prevent the user from signing in to this account with those credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_NO_SUCH_PROVIDER` - If the user does not have a Google Account linked to their account.
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  Future<void> unlinkGoogleCredential() async {
+    return await FirebaseAuth.channel.invokeMethod(
+      'unlinkCredential',
+      <String, String>{'provider': 'google.com', 'app': _app.name},
+    );
+  }
+
+  /// Detaches Facebook from this user.
+  ///
+  /// This detaches the Facebook Account from the current user. This will
+  /// prevent the user from signing in to this account with those credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_NO_SUCH_PROVIDER` - If the user does not have a Facebook Account linked to their account.
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  Future<void> unlinkFacebookCredential() async {
+    return await FirebaseAuth.channel.invokeMethod(
+      'unlinkCredential',
+      <String, String>{'provider': 'facebook.com', 'app': _app.name},
+    );
+  }
+
+  /// Detaches Twitter from this user.
+  ///
+  /// This detaches the Twitter Account from the current user. This will
+  /// prevent the user from signing in to this account with those credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_NO_SUCH_PROVIDER` - If the user does not have a Twitter Account linked to their account.
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  Future<void> unlinkTwitterCredential() async {
+    return await FirebaseAuth.channel.invokeMethod(
+      'unlinkCredential',
+      <String, String>{'provider': 'twitter.com', 'app': _app.name},
+    );
+  }
+
+  /// Detaches Github from this user.
+  ///
+  /// This detaches the Github Account from the current user. This will
+  /// prevent the user from signing in to this account with those credentials.
+  ///
+  /// **Important**: This is a security sensitive operation that requires
+  /// the user to have recently signed in.
+  ///
+  /// Errors:
+  ///   • `ERROR_NO_SUCH_PROVIDER` - If the user does not have a Github Account linked to their account.
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  Future<void> unlinkGithubCredential() async {
+    return await FirebaseAuth.channel.invokeMethod(
+      'unlinkCredential',
+      <String, String>{'provider': 'github.com', 'app': _app.name},
+    );
+  }
+
   @override
   String toString() {
     return '$runtimeType($_data)';
@@ -221,9 +350,11 @@
   /// returned instead. If there is any other existing user signed in, that
   /// user will be signed out.
   ///
-  /// Will throw a PlatformException if
-  /// FIRAuthErrorCodeOperationNotAllowed - Indicates that anonymous accounts are not enabled. Enable them in the Auth section of the Firebase console.
-  /// See FIRAuthErrors for a list of error codes that are common to all API methods.
+  /// **Important**: You must enable Anonymous accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Anonymous accounts are not enabled.
   Future<FirebaseUser> signInAnonymously() async {
     final Map<dynamic, dynamic> data = await channel
         .invokeMethod('signInAnonymously', <String, String>{"app": app.name});
@@ -231,6 +362,15 @@
     return currentUser;
   }
 
+  /// Tries to create a new user account with the given email address and password.
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// Errors:
+  ///   • `ERROR_WEAK_PASSWORD` - If the password is not strong enough.
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the email address is malformed.
+  ///   • `ERROR_EMAIL_ALREADY_IN_USE` - If the email is already in use by a different account.
   Future<FirebaseUser> createUserWithEmailAndPassword({
     @required String email,
     @required String password,
@@ -245,17 +385,33 @@
     return currentUser;
   }
 
-  Future<List<String>> fetchProvidersForEmail({
+  /// Returns a list of sign-in methods that can be used to sign in a given
+  /// user (identified by its main email address).
+  ///
+  /// This method is useful when you support multiple authentication mechanisms
+  /// if you want to implement an email-first authentication flow.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [email] address is malformed.
+  ///   • `ERROR_USER_NOT_FOUND` - If there is no user corresponding to the given [email] address.
+  Future<List<String>> fetchSignInMethodsForEmail({
     @required String email,
   }) async {
     assert(email != null);
     final List<dynamic> providers = await channel.invokeMethod(
-      'fetchProvidersForEmail',
+      'fetchSignInMethodsForEmail',
       <String, String>{'email': email, 'app': app.name},
     );
     return providers?.cast<String>();
   }
 
+  /// Triggers the Firebase Authentication backend to send a password-reset
+  /// email to the given email address, which must correspond to an existing
+  /// user of your app.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_EMAIL` - If the [email] address is malformed.
+  ///   • `ERROR_USER_NOT_FOUND` - If there is no user corresponding to the given [email] address.
   Future<void> sendPasswordResetEmail({
     @required String email,
   }) async {
@@ -266,6 +422,21 @@
     );
   }
 
+  /// Tries to sign in a user with the given email address and password.
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// **Important**: You must enable Email & Password accounts in the Auth
+  /// section of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_EMAIL` - If the [email] address is malformed.
+  ///   • `ERROR_WRONG_PASSWORD` - If the [password] is wrong.
+  ///   • `ERROR_USER_NOT_FOUND` - If there is no user corresponding to the given [email] address, or if the user has been deleted.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_TOO_MANY_REQUESTS` - If there was too many attempts to sign in as this user.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<FirebaseUser> signInWithEmailAndPassword({
     @required String email,
     @required String password,
@@ -280,46 +451,23 @@
     return currentUser;
   }
 
-  Future<FirebaseUser> signInWithFacebook(
-      {@required String accessToken}) async {
-    assert(accessToken != null);
-    final Map<dynamic, dynamic> data = await channel.invokeMethod(
-        'signInWithFacebook',
-        <String, String>{'accessToken': accessToken, 'app': app.name});
-    final FirebaseUser currentUser = FirebaseUser._(data, app);
-    return currentUser;
-  }
-
-  /// Signs in with a Twitter account using the specified credentials.
+  /// Tries to sign in a user with the given Google [idToken] and [accessToken].
   ///
-  /// The returned future completes with the signed-in user or a [PlatformException], if sign in failed.
-  Future<FirebaseUser> signInWithTwitter({
-    @required String authToken,
-    @required String authTokenSecret,
-  }) async {
-    assert(authToken != null);
-    assert(authTokenSecret != null);
-    final Map<dynamic, dynamic> data = await channel.invokeMethod(
-        'signInWithTwitter', <String, String>{
-      'authToken': authToken,
-      'authTokenSecret': authTokenSecret,
-      'app': app.name
-    });
-    final FirebaseUser currentUser = FirebaseUser._(data, app);
-    return currentUser;
-  }
-
-  Future<FirebaseUser> signInWithGithub({@required String token}) async {
-    assert(token != null);
-    final Map<dynamic, dynamic> data =
-        await channel.invokeMethod('signInWithGithub', <String, String>{
-      'token': token,
-      'app': app.name,
-    });
-    final FirebaseUser currentUser = FirebaseUser._(data, app);
-    return currentUser;
-  }
-
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// If the user doesn't have an account already, one will be created automatically.
+  ///
+  /// **Important**: You must enable Google accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [authToken] or [authTokenSecret] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL` - If there already exists an account with the email address asserted by Google.
+  ///       Resolve this case by calling [fetchSignInMethodsForEmail] and then asking the user to sign in using one of them.
+  ///       This error will only be thrown if the "One account per email address" setting is enabled in the Firebase console (recommended).
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Google accounts are not enabled.
   Future<FirebaseUser> signInWithGoogle({
     @required String idToken,
     @required String accessToken,
@@ -331,13 +479,119 @@
       <String, String>{
         'idToken': idToken,
         'accessToken': accessToken,
-        'app': app.name
+        'app': app.name,
       },
     );
     final FirebaseUser currentUser = FirebaseUser._(data, app);
     return currentUser;
   }
 
+  /// Tries to sign in a user with the given Facebook [accessToken].
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// If the user doesn't have an account already, one will be created automatically.
+  ///
+  /// **Important**: You must enable Facebook accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [accessToken] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL` - If there already exists an account with the email address asserted by Facebook.
+  ///       Resolve this case by calling [fetchSignInMethodsForEmail] and then asking the user to sign in using one of them.
+  ///       This error will only be thrown if the "One account per email address" setting is enabled in the Firebase console (recommended).
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Facebook accounts are not enabled.
+  Future<FirebaseUser> signInWithFacebook(
+      {@required String accessToken}) async {
+    assert(accessToken != null);
+    final Map<dynamic, dynamic> data =
+        await channel.invokeMethod('signInWithFacebook', <String, String>{
+      'accessToken': accessToken,
+      'app': app.name,
+    });
+    final FirebaseUser currentUser = FirebaseUser._(data, app);
+    return currentUser;
+  }
+
+  /// Tries to sign in a user with the given Twitter [authToken] and [authTokenSecret].
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// If the user doesn't have an account already, one will be created automatically.
+  ///
+  /// **Important**: You must enable Twitter accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [authToken] or [authTokenSecret] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL` - If there already exists an account with the email address asserted by Twitter.
+  ///       Resolve this case by calling [fetchSignInMethodsForEmail] and then asking the user to sign in using one of them.
+  ///       This error will only be thrown if the "One account per email address" setting is enabled in the Firebase console (recommended).
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Twitter accounts are not enabled.
+  Future<FirebaseUser> signInWithTwitter({
+    @required String authToken,
+    @required String authTokenSecret,
+  }) async {
+    assert(authToken != null);
+    assert(authTokenSecret != null);
+    final Map<dynamic, dynamic> data =
+        await channel.invokeMethod('signInWithTwitter', <String, String>{
+      'authToken': authToken,
+      'authTokenSecret': authTokenSecret,
+      'app': app.name,
+    });
+    final FirebaseUser currentUser = FirebaseUser._(data, app);
+    return currentUser;
+  }
+
+  /// Tries to sign in a user with the given Github [token].
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// If the user doesn't have an account already, one will be created automatically.
+  ///
+  /// **Important**: You must enable Github accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [token] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL` - If there already exists an account with the email address asserted by Github.
+  ///       Resolve this case by calling [fetchSignInMethodsForEmail] and then asking the user to sign in using one of them.
+  ///       This error will only be thrown if the "One account per email address" setting is enabled in the Firebase console (recommended).
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Github accounts are not enabled.
+  Future<FirebaseUser> signInWithGithub({
+    @required String token,
+  }) async {
+    assert(token != null);
+    final Map<dynamic, dynamic> data =
+        await channel.invokeMethod('signInWithGithub', <String, String>{
+      'token': token,
+      'app': app.name,
+    });
+    final FirebaseUser currentUser = FirebaseUser._(data, app);
+    return currentUser;
+  }
+
+  /// Tries to sign in a user with the given Phone [verificationId] and [smsCode].
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// If the user doesn't have an account already, one will be created automatically.
+  ///
+  /// **Important**: You must enable Phone accounts in the Auth section
+  /// of the Firebase console before being able to use them.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [verificationId] or [smsCode] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Phone accounts are not enabled.
   Future<FirebaseUser> signInWithPhoneNumber({
     @required String verificationId,
     @required String smsCode,
@@ -347,13 +601,55 @@
       <String, String>{
         'verificationId': verificationId,
         'smsCode': smsCode,
-        'app': app.name
+        'app': app.name,
       },
     );
     final FirebaseUser currentUser = FirebaseUser._(data, app);
     return currentUser;
   }
 
+  /// Starts the phone number verification process for the given phone number.
+  ///
+  /// Either sends an SMS with a 6 digit code to the phone number specified,
+  /// or sign's the user in and [verificationCompleted] is called.
+  ///
+  /// No duplicated SMS will be sent out upon re-entry (before timeout).
+  ///
+  /// Make sure to test all scenarios below:
+  ///   • You directly get logged in if Google Play Services verified the phone
+  ///     number instantly or helped you auto-retrieve the verification code.
+  ///   • Auto-retrieve verification code timed out.
+  ///   • Error cases when you receive [verificationFailed] callback.
+  ///
+  /// [phoneNumber] The phone number for the account the user is signing up
+  ///   for or signing into. Make sure to pass in a phone number with country
+  ///   code prefixed with plus sign ('+').
+  ///
+  /// [timeout] The maximum amount of time you are willing to wait for SMS
+  ///   auto-retrieval to be completed by the library. Maximum allowed value
+  ///   is 2 minutes. Use 0 to disable SMS-auto-retrieval. Setting this to 0
+  ///   will also cause [codeAutoRetrievalTimeout] to be called immediately.
+  ///   If you specified a positive value less than 30 seconds, library will
+  ///   default to 30 seconds.
+  ///
+  /// [forceResendingToken] The [forceResendingToken] obtained from [codeSent]
+  ///   callback to force re-sending another verification SMS before the
+  ///   auto-retrieval timeout.
+  ///
+  /// [verificationCompleted] This callback must be implemented.
+  ///   It will trigger when an SMS is auto-retrieved or the phone number has
+  ///   been instantly verified. The callback will provide a [FirebaseUser].
+  ///
+  /// [verificationFailed] This callback must be implemented.
+  ///   Triggered when an error occurred during phone number verification.
+  ///
+  /// [codeSent] Optional callback.
+  ///   It will trigger when an SMS has been sent to the users phone,
+  ///   and will include a [verificationId] and [forceResendingToken].
+  ///
+  /// [codeAutoRetrievalTimeout] Optional callback.
+  ///   It will trigger when SMS auto-retrieval times out and provide a
+  ///   [verificationId].
   Future<void> verifyPhoneNumber({
     @required String phoneNumber,
     @required Duration timeout,
@@ -377,12 +673,30 @@
       'phoneNumber': phoneNumber,
       'timeout': timeout.inMilliseconds,
       'forceResendingToken': forceResendingToken,
-      'app': app.name
+      'app': app.name,
     };
 
     await channel.invokeMethod('verifyPhoneNumber', params);
   }
 
+  /// Tries to sign in a user with a given Custom Token [token].
+  ///
+  /// If successful, it also signs the user in into the app and updates
+  /// the [onAuthStateChanged] stream.
+  ///
+  /// Use this method after you retrieve a Firebase Auth Custom Token from your server.
+  ///
+  /// If the user identified by the [uid] specified in the token doesn't
+  /// have an account already, one will be created automatically.
+  ///
+  /// Read how to use Custom Token authentication and the cases where it is
+  /// useful in [the guides](https://firebase.google.com/docs/auth/android/custom-auth).
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CUSTOM_TOKEN` - The custom token format is incorrect.
+  ///     Please check the documentation.
+  ///   • `ERROR_CUSTOM_TOKEN_MISMATCH` - Invalid configuration.
+  ///     Ensure your app's SHA1 is correct in the Firebase console.
   Future<FirebaseUser> signInWithCustomToken({@required String token}) async {
     assert(token != null);
     final Map<dynamic, dynamic> data = await channel.invokeMethod(
@@ -393,12 +707,16 @@
     return currentUser;
   }
 
+  /// Signs out the current user and clears it from the disk cache.
+  ///
+  /// If successful, it signs the user out of the app and updates
+  /// the [onAuthStateChanged] stream.
   Future<void> signOut() async {
     return await channel
         .invokeMethod("signOut", <String, String>{'app': app.name});
   }
 
-  /// Asynchronously gets current user, or `null` if there is none.
+  /// Returns the currently signed-in [FirebaseUser] or [null] if there is none.
   Future<FirebaseUser> currentUser() async {
     final Map<dynamic, dynamic> data = await channel
         .invokeMethod("currentUser", <String, String>{'app': app.name});
@@ -407,12 +725,19 @@
     return currentUser;
   }
 
-  /// Links email account with current user and returns [Future<FirebaseUser>]
-  /// basically current user with additional email information
+  /// Links the given [email] and [password] to the current user.
   ///
-  /// throws [PlatformException] when
-  /// 1. email address is already used
-  /// 2. wrong email and password provided
+  /// This allows the user to sign in to this account in the future with
+  /// the given [email] and [password].
+  ///
+  /// Errors:
+  ///   • `ERROR_WEAK_PASSWORD` - If the password is not strong enough.
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the email address is malformed.
+  ///   • `ERROR_CREDENTIAL_ALREADY_IN_USE` - If the email is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_PROVIDER_ALREADY_LINKED` - If the current user already has an Email & Password linked.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<FirebaseUser> linkWithEmailAndPassword({
     @required String email,
     @required String password,
@@ -427,14 +752,18 @@
     return currentUser;
   }
 
-  /// Links google account with current user and returns [Future<FirebaseUser>]
+  /// Links the Google Account to the current user using [idToken] and [accessToken].
   ///
-  /// throws [PlatformException] when
-  /// 1. No current user provided (user has not logged in)
-  /// 2. No google credentials were found for given [idToken] and [accessToken]
-  /// 3. Google account already linked with another [FirebaseUser]
-  /// Detailed documentation on possible error causes can be found in [Android docs](https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseUser#exceptions_4) and [iOS docs](https://firebase.google.com/docs/reference/ios/firebaseauth/api/reference/Classes/FIRUser#/c:objc(cs)FIRUser(im)linkWithCredential:completion:)
-  /// TODO: Throw custom exceptions with error codes indicating cause of exception
+  /// This allows the user to sign in to this account in the future with
+  /// the given Google Account.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [idToken] or [accessToken] is malformed or has expired.
+  ///   • `ERROR_CREDENTIAL_ALREADY_IN_USE` - If the Google account is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_PROVIDER_ALREADY_LINKED` - If the current user already has a Google account linked.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Google accounts are not enabled.
   Future<FirebaseUser> linkWithGoogleCredential({
     @required String idToken,
     @required String accessToken,
@@ -446,51 +775,107 @@
       <String, String>{
         'idToken': idToken,
         'accessToken': accessToken,
-        'app': app.name
+        'app': app.name,
       },
     );
     final FirebaseUser currentUser = FirebaseUser._(data, app);
     return currentUser;
   }
 
+  /// Links the Facebook Account to the current user using [accessToken].
+  ///
+  /// This allows the user to sign in to this account in the future with
+  /// the given Facebook Account.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [accessToken] is malformed or has expired.
+  ///   • `ERROR_CREDENTIAL_ALREADY_IN_USE` - If the Facebook account is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_PROVIDER_ALREADY_LINKED` - If the current user already has a Facebook account linked.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Facebook accounts are not enabled.
   Future<FirebaseUser> linkWithFacebookCredential({
     @required String accessToken,
   }) async {
     assert(accessToken != null);
     final Map<dynamic, dynamic> data = await channel.invokeMethod(
       'linkWithFacebookCredential',
-      <String, String>{'accessToken': accessToken, 'app': app.name},
-    );
-    final FirebaseUser currentUser = FirebaseUser._(data, app);
-    return currentUser;
-  }
-
-  Future<FirebaseUser> linkWithTwitterCredential({
-    @required String authToken,
-    @required String authTokenSecret,
-  }) async {
-    final Map<dynamic, dynamic> data = await channel.invokeMethod(
-      'linkWithTwitterCredential',
       <String, String>{
+        'accessToken': accessToken,
         'app': app.name,
-        'authToken': authToken,
-        'authTokenSecret': authTokenSecret,
       },
     );
     final FirebaseUser currentUser = FirebaseUser._(data, app);
     return currentUser;
   }
 
-  Future<FirebaseUser> linkWithGithubCredential(
-      {@required String token}) async {
-    assert(token != null);
+  /// Links the Twitter Account to the current user using [authToken] and [authTokenSecret].
+  ///
+  /// This allows the user to sign in to this account in the future with
+  /// the given Twitter Account.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [authToken] or [authTokenSecret] is malformed or has expired.
+  ///   • `ERROR_CREDENTIAL_ALREADY_IN_USE` - If the Twitter account is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_PROVIDER_ALREADY_LINKED` - If the current user already has a Twitter account linked.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Twitter accounts are not enabled.
+  Future<FirebaseUser> linkWithTwitterCredential({
+    @required String authToken,
+    @required String authTokenSecret,
+  }) async {
+    assert(authToken != null);
+    assert(authTokenSecret != null);
     final Map<dynamic, dynamic> data = await channel.invokeMethod(
-        'linkWithGithubCredential',
-        <String, String>{'app': app.name, 'token': token});
+      'linkWithTwitterCredential',
+      <String, String>{
+        'authToken': authToken,
+        'authTokenSecret': authTokenSecret,
+        'app': app.name,
+      },
+    );
     final FirebaseUser currentUser = FirebaseUser._(data, app);
     return currentUser;
   }
 
+  /// Links the Github Account to the current user using [token].
+  ///
+  /// This allows the user to sign in to this account in the future with
+  /// the given Github Account.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [token] is malformed or has expired.
+  ///   • `ERROR_CREDENTIAL_ALREADY_IN_USE` - If the Github account is already in use by a different account.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_REQUIRES_RECENT_LOGIN` - If the user's last sign-in time does not meet the security threshold. Use reauthenticate methods to resolve.
+  ///   • `ERROR_PROVIDER_ALREADY_LINKED` - If the current user already has a Github account linked.
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Github accounts are not enabled.
+  Future<FirebaseUser> linkWithGithubCredential({
+    @required String token,
+  }) async {
+    assert(token != null);
+    final Map<dynamic, dynamic> data = await channel.invokeMethod(
+      'linkWithGithubCredential',
+      <String, String>{
+        'app': app.name,
+        'token': token,
+      },
+    );
+    final FirebaseUser currentUser = FirebaseUser._(data, app);
+    return currentUser;
+  }
+
+  /// Reauthenticates the current user with given [email] and [password].
+  ///
+  /// This is used to prevent or resolve `ERROR_REQUIRES_RECENT_LOGIN`
+  /// response to operations that require a recent sign-in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [email] and/or [password] are incorrect.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> reauthenticateWithEmailAndPassword({
     @required String email,
     @required String password,
@@ -503,6 +888,16 @@
     );
   }
 
+  /// Reauthenticates the current user with the Google Account specified by [idToken] and [accessToken].
+  ///
+  /// This is used to prevent or resolve `ERROR_REQUIRES_RECENT_LOGIN`
+  /// response to operations that require a recent sign-in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [idToken] or [accessToken] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> reauthenticateWithGoogleCredential({
     @required String idToken,
     @required String accessToken,
@@ -519,6 +914,16 @@
     );
   }
 
+  /// Reauthenticates the current user with the Facebook Account specified by [accessToken].
+  ///
+  /// This is used to prevent or resolve `ERROR_REQUIRES_RECENT_LOGIN`
+  /// response to operations that require a recent sign-in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [accessToken] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> reauthenticateWithFacebookCredential({
     @required String accessToken,
   }) {
@@ -529,6 +934,16 @@
     );
   }
 
+  /// Reauthenticates the current user with the Twitter Account specified by [authToken] and [authTokenSecret].
+  ///
+  /// This is used to prevent or resolve `ERROR_REQUIRES_RECENT_LOGIN`
+  /// response to operations that require a recent sign-in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [authToken] or [authTokenSecret] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> reauthenticateWithTwitterCredential({
     @required String authToken,
     @required String authTokenSecret,
@@ -543,10 +958,25 @@
     );
   }
 
+  /// Reauthenticates the current user with the Github Account specified by [token].
+  ///
+  /// This is used to prevent or resolve `ERROR_REQUIRES_RECENT_LOGIN`
+  /// response to operations that require a recent sign-in.
+  ///
+  /// Errors:
+  ///   • `ERROR_INVALID_CREDENTIAL` - If the [token] is malformed or has expired.
+  ///   • `ERROR_USER_DISABLED` - If the user has been disabled (for example, in the Firebase console)
+  ///   • `ERROR_USER_NOT_FOUND` - If the user has been deleted (for example, in the Firebase console)
+  ///   • `ERROR_OPERATION_NOT_ALLOWED` - Indicates that Email & Password accounts are not enabled.
   Future<void> reauthenticateWithGithubCredential({@required String token}) {
     assert(token != null);
-    return channel.invokeMethod('reauthenticateWithGithubCredential',
-        <String, String>{'app': app.name, 'token': token});
+    return channel.invokeMethod(
+      'reauthenticateWithGithubCredential',
+      <String, String>{
+        'app': app.name,
+        'token': token,
+      },
+    );
   }
 
   /// Sets the user-facing language code for auth operations that can be
@@ -560,7 +990,7 @@
     });
   }
 
-  Future<dynamic> _callHandler(MethodCall call) async {
+  Future<void> _callHandler(MethodCall call) async {
     switch (call.method) {
       case 'onAuthStateChanged':
         _onAuthStageChangedHandler(call);
diff --git a/packages/firebase_auth/test/firebase_auth_test.dart b/packages/firebase_auth/test/firebase_auth_test.dart
index 868d4ff..ae83be6 100755
--- a/packages/firebase_auth/test/firebase_auth_test.dart
+++ b/packages/firebase_auth/test/firebase_auth_test.dart
@@ -52,7 +52,7 @@
           case "updateProfile":
             return null;
             break;
-          case "fetchProvidersForEmail":
+          case "fetchSignInMethodsForEmail":
             return List<String>(0);
             break;
           case "verifyPhoneNumber":
@@ -135,16 +135,16 @@
       );
     });
 
-    test('fetchProvidersForEmail', () async {
+    test('fetchSignInMethodsForEmail', () async {
       final List<String> providers =
-          await auth.fetchProvidersForEmail(email: kMockEmail);
+          await auth.fetchSignInMethodsForEmail(email: kMockEmail);
       expect(providers, isNotNull);
       expect(providers.length, 0);
       expect(
         log,
         <Matcher>[
           isMethodCall(
-            'fetchProvidersForEmail',
+            'fetchSignInMethodsForEmail',
             arguments: <String, String>{
               'email': kMockEmail,
               'app': auth.app.name
@@ -154,6 +154,86 @@
       );
     });
 
+    test('linkWithTwitterCredential', () async {
+      final FirebaseUser user = await auth.linkWithTwitterCredential(
+        authToken: kMockIdToken,
+        authTokenSecret: kMockAccessToken,
+      );
+      verifyUser(user);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall(
+            'linkWithTwitterCredential',
+            arguments: <String, String>{
+              'authToken': kMockIdToken,
+              'authTokenSecret': kMockAccessToken,
+              'app': auth.app.name,
+            },
+          ),
+        ],
+      );
+    });
+
+    test('signInWithTwitter', () async {
+      final FirebaseUser user = await auth.signInWithTwitter(
+        authToken: kMockIdToken,
+        authTokenSecret: kMockAccessToken,
+      );
+      verifyUser(user);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall(
+            'signInWithTwitter',
+            arguments: <String, String>{
+              'authToken': kMockIdToken,
+              'authTokenSecret': kMockAccessToken,
+              'app': auth.app.name,
+            },
+          ),
+        ],
+      );
+    });
+
+    test('linkWithGithubCredential', () async {
+      final FirebaseUser user = await auth.linkWithGithubCredential(
+        token: kMockGithubToken,
+      );
+      verifyUser(user);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall(
+            'linkWithGithubCredential',
+            arguments: <String, String>{
+              'token': kMockGithubToken,
+              'app': auth.app.name,
+            },
+          ),
+        ],
+      );
+    });
+
+    test('signInWithGithub', () async {
+      final FirebaseUser user = await auth.signInWithGithub(
+        token: kMockGithubToken,
+      );
+      verifyUser(user);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall(
+            'signInWithGithub',
+            arguments: <String, String>{
+              'token': kMockGithubToken,
+              'app': auth.app.name,
+            },
+          ),
+        ],
+      );
+    });
+
     test('linkWithEmailAndPassword', () async {
       final FirebaseUser user = await auth.linkWithEmailAndPassword(
         email: kMockEmail,
@@ -637,6 +717,96 @@
       ]);
     });
 
+    test('unlinkEmailAndPassword', () async {
+      final FirebaseUser user = await auth.currentUser();
+      await user.unlinkEmailAndPassword();
+      expect(log, <Matcher>[
+        isMethodCall(
+          'currentUser',
+          arguments: <String, String>{'app': auth.app.name},
+        ),
+        isMethodCall(
+          'unlinkCredential',
+          arguments: <String, String>{
+            'app': auth.app.name,
+            'provider': 'password',
+          },
+        ),
+      ]);
+    });
+
+    test('unlinkGoogleCredential', () async {
+      final FirebaseUser user = await auth.currentUser();
+      await user.unlinkGoogleCredential();
+      expect(log, <Matcher>[
+        isMethodCall(
+          'currentUser',
+          arguments: <String, String>{'app': auth.app.name},
+        ),
+        isMethodCall(
+          'unlinkCredential',
+          arguments: <String, String>{
+            'app': auth.app.name,
+            'provider': 'google.com',
+          },
+        ),
+      ]);
+    });
+
+    test('unlinkFacebookCredential', () async {
+      final FirebaseUser user = await auth.currentUser();
+      await user.unlinkFacebookCredential();
+      expect(log, <Matcher>[
+        isMethodCall(
+          'currentUser',
+          arguments: <String, String>{'app': auth.app.name},
+        ),
+        isMethodCall(
+          'unlinkCredential',
+          arguments: <String, String>{
+            'app': auth.app.name,
+            'provider': 'facebook.com',
+          },
+        ),
+      ]);
+    });
+
+    test('unlinkTwitterCredential', () async {
+      final FirebaseUser user = await auth.currentUser();
+      await user.unlinkTwitterCredential();
+      expect(log, <Matcher>[
+        isMethodCall(
+          'currentUser',
+          arguments: <String, String>{'app': auth.app.name},
+        ),
+        isMethodCall(
+          'unlinkCredential',
+          arguments: <String, String>{
+            'app': auth.app.name,
+            'provider': 'twitter.com',
+          },
+        ),
+      ]);
+    });
+
+    test('unlinkGithubCredential', () async {
+      final FirebaseUser user = await auth.currentUser();
+      await user.unlinkGithubCredential();
+      expect(log, <Matcher>[
+        isMethodCall(
+          'currentUser',
+          arguments: <String, String>{'app': auth.app.name},
+        ),
+        isMethodCall(
+          'unlinkCredential',
+          arguments: <String, String>{
+            'app': auth.app.name,
+            'provider': 'github.com',
+          },
+        ),
+      ]);
+    });
+
     test('signInWithCustomToken', () async {
       final FirebaseUser user =
           await auth.signInWithCustomToken(token: kMockCustomToken);