Browse Source

Crash reporting fixes and updates

(plus some library updates)
pull/24/head
Ezerous 8 years ago
parent
commit
402d081c0e
  1. 2
      .gitlab-ci.yml
  2. 20
      app/build.gradle
  3. 6
      app/src/main/assets/apache_libraries.html
  4. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  5. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  6. 19
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  7. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  8. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  9. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  10. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  11. 7
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
  12. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  13. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  14. 2
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  15. 6
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  16. 6
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  17. 21
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java
  18. 14
      app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java
  19. 2
      build.gradle

2
.gitlab-ci.yml

@ -2,7 +2,7 @@ image: openjdk:8-jdk
variables: variables:
ANDROID_TARGET_SDK: "25" ANDROID_TARGET_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.2" ANDROID_BUILD_TOOLS: "25.0.3"
ANDROID_SDK_TOOLS: "25.2.5" ANDROID_SDK_TOOLS: "25.2.5"
before_script: before_script:

20
app/build.gradle

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 25 compileSdkVersion 25
buildToolsVersion "25.0.2" buildToolsVersion "25.0.3"
defaultConfig { defaultConfig {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -28,25 +28,25 @@ android {
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.0' compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.0' compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4:25.3.0' compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:cardview-v7:25.3.0' compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.0' compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.google.firebase:firebase-crash:10.2.0' compile 'com.google.firebase:firebase-crash:11.0.0'
compile 'com.squareup.okhttp3:okhttp:3.6.0' compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'org.jsoup:jsoup:1.10.2' compile 'org.jsoup:jsoup:1.10.2'
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0' compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.1' compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
compile('com.mikepenz:materialdrawer:5.8.2@aar') { compile('com.mikepenz:materialdrawer:5.9.2@aar') {
transitive = true transitive = true
} }
compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.5' compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.5'
compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'
compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile 'me.zhanghai.android.materialprogressbar:library:1.4.1'
compile 'com.jakewharton.timber:timber:4.5.1' compile 'com.jakewharton.timber:timber:4.5.1'
} }

6
app/src/main/assets/apache_libraries.html

@ -39,7 +39,7 @@
<body> <body>
<ul> <ul>
<li> <li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.6.0 (Copyright ©2016 Square, Inc.)</h5> <h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.8.0 (Copyright ©2016 Square, Inc.)</h5>
</li> </li>
<li> <li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5> <h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
@ -51,13 +51,13 @@
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.1 (Copyright ©2016 Philipp Jahoda)</h5> <h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.1 (Copyright ©2016 Philipp Jahoda)</h5>
</li> </li>
<li> <li>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.8.2 (Copyright ©2016 Mike Penz)</h5> <h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.9.2 (Copyright ©2016 Mike Penz)</h5>
</li> </li>
<li> <li>
<h5><a href="https://github.com/mikepenz/Android-Iconics/tree/develop/fontawesome-typeface-library">Fontawesome Typeface Library</a>&nbsp;v4.7.0.0 (Copyright ©2016 Mike Penz)</h5> <h5><a href="https://github.com/mikepenz/Android-Iconics/tree/develop/fontawesome-typeface-library">Fontawesome Typeface Library</a>&nbsp;v4.7.0.0 (Copyright ©2016 Mike Penz)</h5>
</li> </li>
<li> <li>
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.3.0 (Copyright ©2015 Zhang Hai)</h5> <h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.4.1 (Copyright ©2015 Zhang Hai)</h5>
</li> </li>
</ul> </ul>

2
app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java

@ -194,7 +194,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Timber.e("ERROR", e); Timber.e(e, "Exception");
} }
return null; return null;
} }

2
app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java

@ -189,7 +189,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Timber.e("ERROR", e); Timber.e(e, "Exception");
} }
return null; return null;
} }

19
app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java

@ -29,6 +29,7 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category; import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -178,12 +179,15 @@ public class ForumFragment extends BaseFragment
categories.addAll(fetchedCategories); categories.addAll(fetchedCategories);
fetchedCategories.clear(); fetchedCategories.clear();
return 0; return 0;
} catch (IOException e) { } catch (ParseException e) {
Timber.d("Network Error", e); Timber.e(e, "ParseException");
return 1; return 1;
} catch (Exception e) { } catch (IOException e) {
Timber.d("Exception", e); Timber.i(e, "Network Error");
return 2; return 2;
} catch (Exception e) {
Timber.e(e, "Exception");
return 3;
} }
} }
@ -193,14 +197,13 @@ public class ForumFragment extends BaseFragment
if (result == 0) if (result == 0)
forumAdapter.notifyParentDataSetChanged(false); forumAdapter.notifyParentDataSetChanged(false);
else if (result == 1) else if (result == 2)
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
} }
private void parse(Document document) private void parse(Document document) throws ParseException {
{
Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]"); Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]");
if (categoryBlocks.size() != 0) { if (categoryBlocks.size() != 0) {
for(Element categoryBlock: categoryBlocks) for(Element categoryBlock: categoryBlocks)
@ -225,7 +228,7 @@ public class ForumFragment extends BaseFragment
} }
} }
else else
Timber.e("Parsing failed!"); throw new ParseException("Parsing failed");
} }
public void setUrl(String string) public void setUrl(String string)

6
app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java

@ -161,13 +161,13 @@ public class RecentFragment extends BaseFragment {
parse(document); parse(document);
return 0; return 0;
} catch (ParseException e) { } catch (ParseException e) {
Timber.e("ParseException", e); Timber.e(e, "ParseException");
return 1; return 1;
} catch (IOException e) { } catch (IOException e) {
Timber.i("Network Error", e); Timber.i(e, "Network Error");
return 2; return 2;
} catch (Exception e) { } catch (Exception e) {
Timber.e("Exception", e); Timber.e(e, "Exception");
return 3; return 3;
} }

3
app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java

@ -276,11 +276,12 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Timber.e("ERROR", e); Timber.e(e, "Exception");
} }
return false; return false;
} }
//TODO: better parse error handling (ParseException etc.)
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! //TODO report as ParseException? if (!result) { //Parse failed! //TODO report as ParseException?
Timber.d("Parse failed!"); Timber.d("Parse failed!");

3
app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java

@ -167,7 +167,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Timber.e("ERROR", e); Timber.e(e, "Exception");
} }
return false; return false;
} }
@ -185,6 +185,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
isLoadingMore = false; isLoadingMore = false;
} }
//TODO: better parse error handling (ParseException etc.)
private boolean parseLatestPosts(Document latestPostsPage) { private boolean parseLatestPosts(Document latestPostsPage) {
Elements latestPostsRows = latestPostsPage. Elements latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Show Posts)):not([style]) > table"); select("td:has(table:Contains(Show Posts)):not([style]) > table");

3
app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java

@ -141,11 +141,12 @@ public class StatsFragment extends Fragment {
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Timber.e("ERROR", e); Timber.e(e, "Exception");
} }
return false; return false;
} }
//TODO: better parse error handling (ParseException etc.)
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! if (!result) { //Parse failed!

7
app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java

@ -1,7 +1,5 @@
package gr.thmmy.mthmmy.activities.topic; package gr.thmmy.mthmmy.activities.topic;
import android.util.Log;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@ -11,6 +9,7 @@ import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
class Posting { class Posting {
enum REPLY_STATUS { enum REPLY_STATUS {
@ -26,8 +25,8 @@ class Posting {
String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first() String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first()
.toString().split("<br>"); .toString().split("<br>");
for (int i = 0; i < errors.length; ++i) { //TODO test for (int i = 0; i < errors.length; ++i) { //TODO test
Log.d("TAG", String.valueOf(i)); Timber.d(String.valueOf(i));
Log.d("TAG", errors[i]); Timber.d(errors[i]);
} }
for (String error : errors) { for (String error : errors) {
if (error.contains("Your session timed out while posting") || if (error.contains("Your session timed out while posting") ||

8
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java

@ -134,7 +134,7 @@ public class TopicActivity extends BaseActivity {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory( ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl)); Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) { if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
Timber.e("Bundle came with a non topic url!\nUrl:\n" + topicPageUrl); Timber.e("Bundle came with a non topic url!\nUrl: %s" , topicPageUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -517,10 +517,10 @@ public class TopicActivity extends BaseActivity {
parse(document); parse(document);
return SUCCESS; return SUCCESS;
} catch (IOException e) { } catch (IOException e) {
Timber.i("IO Exception", e); Timber.i(e, "IO Exception");
return NETWORK_ERROR; return NETWORK_ERROR;
} catch (Exception e) { } catch (Exception e) {
Timber.e("Exception", e); Timber.e(e, "Exception");
return OTHER_ERROR; return OTHER_ERROR;
} }
} }
@ -568,7 +568,7 @@ public class TopicActivity extends BaseActivity {
break; break;
default: default:
//Parse failed - should never happen //Parse failed - should never happen
Timber.d("Parse failed!"); //TODO report ParseException? Timber.d("Parse failed!"); //TODO report ParseException!!!
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish(); finish();
break; break;

2
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java

@ -259,7 +259,7 @@ class TopicParser {
try { try {
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href"));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Timber.e("Attached file malformed url", e); Timber.e(e, "Attached file malformed url");
break; break;
} }
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1);

2
app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java

@ -162,7 +162,7 @@ public class ThmmyPage {
|| Objects.equals(uriString, "https://www.thmmy.gr") || Objects.equals(uriString, "https://www.thmmy.gr")
|| Objects.equals(uriString, "https://www.thmmy.gr/smf/index.php")) || Objects.equals(uriString, "https://www.thmmy.gr/smf/index.php"))
return PageCategory.INDEX; return PageCategory.INDEX;
Timber.v("Unknown thmmy link found, link: " + uriString); Timber.v("Unknown thmmy link found, link: %s" , uriString);
return PageCategory.UNKNOWN_THMMY; return PageCategory.UNKNOWN_THMMY;
} }
return PageCategory.NOT_THMMY; return PageCategory.NOT_THMMY;

6
app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java

@ -142,7 +142,7 @@ public class DownloadService extends IntentService {
fileName = file.getName(); fileName = file.getName();
Timber.v("Started saving file " + fileName); Timber.v("Started saving file %s" , fileName);
sendNotification(downloadId, STARTED, fileName); sendNotification(downloadId, STARTED, fileName);
sink = Okio.buffer(Okio.sink(file)); sink = Okio.buffer(Okio.sink(file));
@ -154,11 +154,11 @@ public class DownloadService extends IntentService {
Timber.e("Response not a binary file!"); Timber.e("Response not a binary file!");
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Timber.i("Download failed..."); Timber.i("Download failed...");
Timber.e("FileNotFound", e); Timber.e(e, "FileNotFound");
sendNotification(downloadId, FAILED, fileName); sendNotification(downloadId, FAILED, fileName);
} catch (IOException e) { } catch (IOException e) {
Timber.i("Download failed..."); Timber.i("Download failed...");
Timber.e("IOException", e); Timber.e(e, "IOException");
sendNotification(downloadId, FAILED, fileName); sendNotification(downloadId, FAILED, fileName);
} finally { } finally {
if (sink != null) { if (sink != null) {

6
app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java

@ -154,10 +154,10 @@ public class SessionManager {
Timber.i("Login InterruptedIOException"); //users cancels LoginTask Timber.i("Login InterruptedIOException"); //users cancels LoginTask
return CANCELLED; return CANCELLED;
} catch (IOException e) { } catch (IOException e) {
Timber.w("Login IOException", e); Timber.w(e ,"Login IOException");
return CONNECTION_ERROR; return CONNECTION_ERROR;
} catch (Exception e) { } catch (Exception e) {
Timber.w("Login Exception (other)", e); Timber.e(e, "Login Exception (other)");
return EXCEPTION; return EXCEPTION;
} }
} }
@ -223,7 +223,7 @@ public class SessionManager {
Timber.w("Logout IOException", e); Timber.w("Logout IOException", e);
return CONNECTION_ERROR; return CONNECTION_ERROR;
} catch (Exception e) { } catch (Exception e) {
Timber.w("Logout Exception", e); Timber.e(e, "Logout Exception");
return EXCEPTION; return EXCEPTION;
} finally { } finally {
//All data should always be cleared from device regardless the result of logout //All data should always be cleared from device regardless the result of logout

21
app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java

@ -4,17 +4,17 @@ import android.util.Log;
import com.google.firebase.crash.FirebaseCrash; import com.google.firebase.crash.FirebaseCrash;
import gr.thmmy.mthmmy.utils.exceptions.UnknownException; import timber.log.Timber.DebugTree;
import timber.log.Timber;
public class CrashReportingTree extends Timber.Tree { public class CrashReportingTree extends DebugTree {
@Override @Override
protected void log(int priority, String tag, String message, Throwable t) { protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) { if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return; return;
} }
String level="A"; String level;
if (priority == Log.INFO) if (priority == Log.INFO)
level = "I"; level = "I";
@ -22,13 +22,18 @@ public class CrashReportingTree extends Timber.Tree {
level = "W"; level = "W";
else if(priority == Log.ERROR) else if(priority == Log.ERROR)
level = "E"; level = "E";
else
level = "A";
FirebaseCrash.log(level + "/" + tag + ": " + message); FirebaseCrash.log(level + "/" + tag + ": " + message);
if(t==null) if(priority == Log.ERROR)
t = new UnknownException("UnknownException"); {
if (t!=null)
FirebaseCrash.report(t);
else
FirebaseCrash.report(new Exception(message));
}
if ((priority == Log.ERROR))
FirebaseCrash.report(t);
} }
} }

14
app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java

@ -1,14 +0,0 @@
package gr.thmmy.mthmmy.utils.exceptions;
/**
* UnknownException is thrown upon an error (see Report.java in release), when no other specific
* exception is set, to report to FireBase.
*/
public class UnknownException extends Exception {
public UnknownException() {
}
public UnknownException(String message) {
super(message);
}
}

2
build.gradle

@ -6,7 +6,7 @@ buildscript {
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.google.gms:google-services:3.0.0' classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

Loading…
Cancel
Save