Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 0 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1 @@
# Desafio de programação mobile Android

A ideia deste desafio é nos permitir avaliar melhor as habilidades de candidatos a vagas de programador, de vários níveis.

Este desafio deve ser feito por você em sua casa. Gaste o tempo que você quiser, porém normalmente você não deve precisar de mais do que algumas horas.

## Instruções de entrega do desafio

1. Primeiro, faça um fork deste projeto para sua conta no GitHub (crie uma se você não possuir).
1. Em seguida, implemente o projeto tal qual descrito abaixo, em seu próprio fork.
1. Por fim, empurre todas as suas alterações para o seu fork no GitHub e envie um pull request para este repositório original. Se você já entrou em contato com alguém da AppProva sobre uma vaga, avise também essa pessoa por email, incluindo no email o seu usuário no GitHub.

## Descrição do projeto

Você deve criar um aplicativo que irá listar os repositórios públicos mais populares relacionados a Java no GitHub, usando a [API do GitHub](https://developer.github.com/v3/) para buscar os dados necessários.

O aplicativo deve exibir inicialmente uma lista paginada dos repositórios, ordenados por popularidade decrescente (exemplo de chamada da API: `https://api.github.com/search/repositories?q=language:Java&sort=stars&page=1`).

Cada repositório deve exibir Nome do repositório, Descrição do Repositório, Nome / Foto do autor, Número de Stars, Número de Forks.

Ao tocar em um item, deve levar a lista de Pull Requests do repositório. Cada item da lista deve exibir Nome / Foto do autor do PR, Título do PR, Data do PR e Body do PR.

Ao tocar em um item, deve abrir no browser a página do Pull Request em questão.

Você pode se basear neste mockup para criar as telas:

![mockup](https://raw.githubusercontent.com/myfreecomm/desafio-mobile-android/master/mockup-android.png)

Sua aplicação deve:

- usar um sistema de build. Ex: Gradle
- fazer mapeamento json -> Objeto . Ex: GSON / Jackson / Moshi / etc
- usar um arquivo .gitignore no seu repositório
- usar Material Design
- usar o padrão MVP ou MVVM
- possuir boa cobertura de testes unitários no projeto.

Você ganha mais pontos se:

- criar testes funcionais
- fazer cache de imagens
- utilizar Kotlin
- utilizar RxJava ou RxKotlin
- utilizar os Android Architecture Components
- suportar mudanças de orientação das telas sem perder estado (Sem utilizar o configChanges)

As sugestões de bibliotecas fornecidas são só um guideline, sinta-se a vontade para usar soluções diferentes e nos surpreender. O importante de fato é que os objetivos macros sejam atingidos.

## Avaliação

Seu projeto será avaliado de acordo com os seguintes critérios.

1. Sua aplicação preenche os requerimentos básicos?
1. Você documentou a maneira de configurar o ambiente e rodar sua aplicação?
1. Você seguiu as instruções de envio do desafio?

Adicionalmente, tentaremos verificar a sua familiarização com as bibliotecas padrões (standard libs), bem como sua experiência com programação orientada a objetos a partir da estrutura de seu projeto.
9 changes: 9 additions & 0 deletions desafio-mobile-android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
1 change: 1 addition & 0 deletions desafio-mobile-android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
66 changes: 66 additions & 0 deletions desafio-mobile-android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "com.app.desafio_mobile_android"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:recyclerview-v7:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"

compile "com.android.support.constraint:constraint-layout:$rootProject.constraint"

compile "com.squareup.retrofit2:retrofit:$rootProject.retrofit"
compile "com.squareup.okhttp3:logging-interceptor:$rootProject.okhttp"
compile "com.squareup.okhttp3:okhttp:$rootProject.okhttp"
compile "com.squareup.retrofit2:converter-gson:$rootProject.gson"

compile "com.jakewharton:butterknife:$rootProject.butterknife"
annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.butterknife"

compile "com.squareup.picasso:picasso:$rootProject.picasso"

compile "com.lusfold.spinnerloading:library:$rootProject.spinnerloading"

androidTestCompile "junit:junit:$rootProject.ext.junitVersion"
androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion", {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powermockVersion"
androidTestCompile "org.powermock:powermock-module-junit4-rule:$rootProject.ext.powermockVersion"
androidTestCompile ("com.android.support.test:runner:0.5", {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion"


testCompile "junit:junit:$rootProject.ext.junitVersion"
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powermockVersion"
testCompile "org.powermock:powermock-api-mockito:$rootProject.ext.powermockVersion"
testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestAllVersion"

androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"


}
25 changes: 25 additions & 0 deletions desafio-mobile-android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/selem/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.app.desafio_mobile_android;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("com.app.desafio_mobile_android", appContext.getPackageName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.app.desafio_mobile_android;


import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import com.app.desafio_mobile_android.presentation.home.MainActivity;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

@Rule
public ActivityTestRule<MainActivity> mMainActivityActivityTestRule = new
ActivityTestRule<>(MainActivity.class, false, true);

@Test
public void whenActivityIsLaunched_shouldDisplayInitialState() {
onView(withId(R.id.recyclerview_repositorie_list)).check(matches(isDisplayed()));
}
}
32 changes: 32 additions & 0 deletions desafio-mobile-android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.desafio_mobile_android">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".presentation.home.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".presentation.pull.PullActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"/>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.app.desafio_mobile_android.business;

import android.content.Context;

import com.app.desafio_mobile_android.intrastructure.error.OperationError;
import com.app.desafio_mobile_android.intrastructure.util.NetworkUtil;
import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
import com.app.desafio_mobile_android.presentation.pull.PullAdapter;
import com.app.desafio_mobile_android.presentation.pull.PullPresenter;
import com.app.desafio_mobile_android.presentation.pull.PullViewModel;
import com.app.desafio_mobile_android.service.Api;
import com.app.desafio_mobile_android.service.ApiInstance;
import com.app.desafio_mobile_android.service.model.pull.Pull;
import com.app.desafio_mobile_android.service.repository.PullRepository;

import java.util.List;

public class PullBusiness {
private PullRepository mPullRepository;
private ServiceListener<PullViewModel> mCallback;

public PullBusiness(Context context) {
mPullRepository = new PullRepository(ApiInstance.getAPI().create(Api.class),
new NetworkUtil(context));
}

public void callPullList(String owner, String repo, ServiceListener<PullViewModel> callback) {
mCallback = callback;
mPullRepository.requestRepositoryList(owner, repo, new RequestPullCallback());
}

private void processResult(List<Pull> pullList) {
int opened = 0;
int closed = 0;
for (Pull pull : pullList) {
if (pull.isClosed()) {
closed++;
} else {
opened++;
}
}
mCallback.onServiceSuccess(new PullViewModel(pullList, opened, closed));
}

private class RequestPullCallback extends ServiceListener<List<Pull>> {

@Override
public void onServiceSuccess(List<Pull> pullList) {
processResult(pullList);
}

@Override
public void onServiceError(OperationError error) {
mCallback.onServiceError(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.app.desafio_mobile_android.business;

import android.content.Context;

import com.app.desafio_mobile_android.intrastructure.util.NetworkUtil;
import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
import com.app.desafio_mobile_android.service.Api;
import com.app.desafio_mobile_android.service.ApiInstance;
import com.app.desafio_mobile_android.service.model.repositorie.Repositorie;
import com.app.desafio_mobile_android.service.repository.RepositorieRepository;

public class RepositorieBusiness {

private RepositorieRepository mRepositorieRepository;

public RepositorieBusiness(Context context) {
mRepositorieRepository = new RepositorieRepository(ApiInstance.getAPI().create(Api.class),
new NetworkUtil(context));
}

public void callServiceRepositorie(int pageNumber, ServiceListener<Repositorie> callback) {
mRepositorieRepository.requestRepositoryList(pageNumber, callback);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.app.desafio_mobile_android.intrastructure;

public class Constants {

public static final String ABOUT_URL = "https://www.linkedin.com/in/selem-afonso-a0b75a8";
public static final String BASE_API = "https://api.github.com/";
public enum ErrorType {
NETWORK_ERROR, SERVICE_ERROR, GENERIC_ERROR, CUSTOM_ERROR
}

public static class DatePattern {
public static final String DATE_ENGLISH = "yyyy-MM-dd'T'HH:mm:ss";
public static final String DEFAULT_FORMAT = "MM/dd/yyyy hh:mm:ss aa";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.app.desafio_mobile_android.intrastructure.error;


import com.app.desafio_mobile_android.intrastructure.Constants;

public class OperationError {

private Constants.ErrorType errorType;
private String errorMsg;

public Constants.ErrorType getErrorType() {
return errorType;
}

public void setErrorType(Constants.ErrorType errorType) {
this.errorType = errorType;
}

public String getErrorMsg() {
return errorMsg;
}

public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}

public boolean isCustomError() {
return errorType == Constants.ErrorType.CUSTOM_ERROR;
}
}
Loading