Added modules: api-logansquare, lifecycle-common, lifecycle-rx
This commit is contained in:
parent
3a990daa86
commit
4ea6ed857b
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":storable")
|
||||
api 'net.danlew:android.joda:2.9.9.4'
|
||||
|
||||
implementation "com.android.support:support-annotations:$versions.supportLibrary"
|
||||
implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
|
||||
implementation 'ru.touchin:logansquare:1.4.3'
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.templates.logansquare"/>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.bluelinelabs.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Utility class for the {@link ru.touchin.templates.logansquare.LoganSquareJsonFactory}. This resides in LoganSquare's
|
||||
* main package in order to take advantage of the package-visible ConcreteParameterizedType class, which is essential
|
||||
* to the support of generic classes in the Retrofit converter.
|
||||
*/
|
||||
public final class ConverterUtils {
|
||||
|
||||
@NonNull
|
||||
public static ParameterizedType parameterizedTypeOf(@NonNull final Type type) {
|
||||
return new ParameterizedType.ConcreteParameterizedType(type);
|
||||
}
|
||||
|
||||
private ConverterUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates;
|
||||
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 11/08/2016.
|
||||
* Just model from getting from API.
|
||||
*/
|
||||
public abstract class ApiModel implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Logging group to log API validation errors.
|
||||
*/
|
||||
public static final LcGroup API_VALIDATION_LC_GROUP = new LcGroup("API_VALIDATION");
|
||||
|
||||
/**
|
||||
* Validates list of objects. Use it if objects in list extends {@link ApiModel}.
|
||||
*
|
||||
* @param collection Collection of items to check;
|
||||
* @param collectionValidationRule Rule explaining what to do if invalid items found;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.PreserveStackTrace", "PMD.CyclomaticComplexity"})
|
||||
// PreserveStackTrace: it's ok - we are logging it on Lc.e()
|
||||
public static void validateCollection(@NonNull final Collection collection, @NonNull final CollectionValidationRule collectionValidationRule)
|
||||
throws ValidationException {
|
||||
boolean haveValidItem = false;
|
||||
int position = 0;
|
||||
final Iterator iterator = collection.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Object item = iterator.next();
|
||||
if (!(item instanceof ApiModel)) {
|
||||
if (item != null) {
|
||||
// let's just think that all of items are not ApiModels
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
((ApiModel) item).validate();
|
||||
haveValidItem = true;
|
||||
} catch (final ValidationException exception) {
|
||||
switch (collectionValidationRule) {
|
||||
case EXCEPTION_IF_ANY_INVALID:
|
||||
throw exception;
|
||||
case EXCEPTION_IF_ALL_INVALID:
|
||||
iterator.remove();
|
||||
API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
|
||||
if (!iterator.hasNext() && !haveValidItem) {
|
||||
throw new ValidationException("Whole list is invalid at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
break;
|
||||
case REMOVE_INVALID_ITEMS:
|
||||
iterator.remove();
|
||||
API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
|
||||
break;
|
||||
default:
|
||||
Lc.assertion("Unexpected rule " + collectionValidationRule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates collection on emptiness.
|
||||
*
|
||||
* @param collection Collection to check;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
protected static void validateCollectionNotEmpty(@NonNull final Collection collection)
|
||||
throws ValidationException {
|
||||
if (collection.isEmpty()) {
|
||||
throw new ValidationException("List is empty at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates this object. Override it to write your own logic.
|
||||
*
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
@CallSuper
|
||||
public void validate() throws ValidationException {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
public enum CollectionValidationRule {
|
||||
EXCEPTION_IF_ANY_INVALID,
|
||||
EXCEPTION_IF_ALL_INVALID,
|
||||
REMOVE_INVALID_ITEMS,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class of exceptions throws during {@link ApiModel} validation.
|
||||
*/
|
||||
public static class ValidationException extends IOException {
|
||||
|
||||
public ValidationException(@NonNull final String reason) {
|
||||
super(reason);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* LoganSquare converter for java.math.BigDecimal
|
||||
*/
|
||||
@SuppressWarnings("CPD-START") // similar to LoganSquareJodaTimeConverter
|
||||
public class LoganSquareBigDecimalConverter implements TypeConverter<BigDecimal> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BigDecimal parse(@NonNull final JsonParser jsonParser) throws IOException {
|
||||
final String dateString = jsonParser.getValueAsString();
|
||||
if (dateString == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(dateString);
|
||||
} catch (final RuntimeException exception) {
|
||||
Lc.assertion(exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(@Nullable final BigDecimal object,
|
||||
@Nullable final String fieldName,
|
||||
final boolean writeFieldNameForObject,
|
||||
@NonNull final JsonGenerator jsonGenerator)
|
||||
throws IOException {
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeStringField(fieldName, object != null ? object.toString() : null);
|
||||
} else {
|
||||
jsonGenerator.writeString(object != null ? object.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov.
|
||||
* LoganSquare enum base class.
|
||||
*/
|
||||
public interface LoganSquareEnum {
|
||||
|
||||
@NonNull
|
||||
String getValueName();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov.
|
||||
* LoganSquare converter from String to Enum.
|
||||
*/
|
||||
@SuppressWarnings("PMD.UseVarargs")
|
||||
public class LoganSquareEnumConverter<T extends Enum & LoganSquareEnum> extends StringBasedTypeConverter<T> {
|
||||
|
||||
@NonNull
|
||||
private final T[] enumValues;
|
||||
@Nullable
|
||||
private final T defaultValue;
|
||||
|
||||
public LoganSquareEnumConverter(@NonNull final T[] enumValues) {
|
||||
this(enumValues, null);
|
||||
}
|
||||
|
||||
public LoganSquareEnumConverter(@NonNull final T[] enumValues, @Nullable final T defaultValue) {
|
||||
super();
|
||||
this.enumValues = enumValues;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public T getFromString(@Nullable final String string) {
|
||||
if (string == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
for (final T value : enumValues) {
|
||||
if (value.getValueName().equals(string)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
throw new ShouldNotHappenException("Enum parsing exception for value: " + string);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String convertToString(@Nullable final T object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
return object.getValueName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* LoganSquare converter for joda.time.DateTime
|
||||
*/
|
||||
public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
|
||||
|
||||
@Nullable
|
||||
private final DateTimeFormatter formatter;
|
||||
|
||||
public LoganSquareJodaTimeConverter() {
|
||||
this.formatter = null;
|
||||
}
|
||||
|
||||
public LoganSquareJodaTimeConverter(@Nullable final DateTimeFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException {
|
||||
final String dateString = jsonParser.getValueAsString();
|
||||
if (dateString == null || dateString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateTime.parse(dateString);
|
||||
} catch (final RuntimeException exception) {
|
||||
Lc.assertion(exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(
|
||||
@Nullable final DateTime object,
|
||||
@Nullable final String fieldName,
|
||||
final boolean writeFieldNameForObject,
|
||||
@NonNull final JsonGenerator jsonGenerator
|
||||
) throws IOException {
|
||||
final String serializedValue = object != null ? object.toString(formatter) : null;
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeStringField(fieldName, !TextUtils.isEmpty(serializedValue) ? serializedValue : null);
|
||||
} else {
|
||||
jsonGenerator.writeString(!TextUtils.isEmpty(serializedValue) ? serializedValue : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.ConverterUtils;
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Converter;
|
||||
import retrofit2.Retrofit;
|
||||
import ru.touchin.templates.retrofit.JsonRequestBodyConverter;
|
||||
import ru.touchin.templates.retrofit.JsonResponseBodyConverter;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 2/06/2016.
|
||||
* LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Logan Square library.
|
||||
*/
|
||||
public class LoganSquareJsonFactory extends Converter.Factory {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Converter<ResponseBody, ?> responseBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] annotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new LoganSquareJsonResponseBodyConverter<>(type);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Converter<?, RequestBody> requestBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] parameterAnnotations,
|
||||
@NonNull final Annotation[] methodAnnotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new LoganSquareRequestBodyConverter<>();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Converter<?, String> stringConverter(@NonNull final Type type, @NonNull final Annotation[] annotations, @NonNull final Retrofit retrofit) {
|
||||
if (type instanceof Class && ((Class) type).getSuperclass() == Enum.class) {
|
||||
return new LoganSquareStringEnumConverter<>();
|
||||
} else {
|
||||
return super.stringConverter(type, annotations, retrofit);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoganSquareJsonResponseBodyConverter<T> extends JsonResponseBodyConverter<T> {
|
||||
|
||||
@NonNull
|
||||
private final Type type;
|
||||
|
||||
public LoganSquareJsonResponseBodyConverter(@NonNull final Type type) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NonNull
|
||||
@Override
|
||||
protected T parseResponse(@NonNull final ResponseBody value) throws IOException {
|
||||
if (type instanceof ParameterizedType) {
|
||||
final ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
final Type[] typeArguments = parameterizedType.getActualTypeArguments();
|
||||
final Type firstType = typeArguments[0];
|
||||
|
||||
final Type rawType = parameterizedType.getRawType();
|
||||
if (rawType == Map.class) {
|
||||
return (T) LoganSquare.parseMap(value.byteStream(), (Class<?>) typeArguments[1]);
|
||||
} else if (rawType == List.class) {
|
||||
return (T) LoganSquare.parseList(value.byteStream(), (Class<?>) firstType);
|
||||
} else {
|
||||
// Generics
|
||||
return (T) LoganSquare.parse(value.byteStream(), ConverterUtils.parameterizedTypeOf(type));
|
||||
}
|
||||
} else {
|
||||
return (T) LoganSquare.parse(value.byteStream(), (Class) type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class LoganSquareRequestBodyConverter<T> extends JsonRequestBodyConverter<T> {
|
||||
|
||||
@Override
|
||||
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException {
|
||||
LoganSquare.serialize(value, byteArrayOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class LoganSquareStringEnumConverter<T> implements Converter<T, String> {
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"unchecked", "TryFinallyCanBeTryWithResources"})
|
||||
@Override
|
||||
public String convert(@NonNull final T value) throws IOException {
|
||||
final StringWriter writer = new StringWriter();
|
||||
try {
|
||||
final JsonGenerator generator = LoganSquare.JSON_FACTORY.createGenerator(writer);
|
||||
LoganSquare.typeConverterFor((Class<T>) value.getClass()).serialize(value, null, false, generator);
|
||||
generator.close();
|
||||
return writer.toString().replaceAll("\"", "");
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov.
|
||||
* Just model from getting from API via LoganSquare.
|
||||
*/
|
||||
public abstract class LoganSquareJsonModel extends ApiModel {
|
||||
|
||||
/**
|
||||
* Throws exception if object is missed or null.
|
||||
*
|
||||
* @param object Value of field to check;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
protected static void validateNotNull(@Nullable final Object object) throws ValidationException {
|
||||
if (object == null) {
|
||||
throw new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.storables.PreferenceStore;
|
||||
import ru.touchin.roboswag.core.observables.storable.Converter;
|
||||
import ru.touchin.roboswag.core.observables.storable.NonNullStorable;
|
||||
import ru.touchin.roboswag.core.observables.storable.Storable;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 26/12/2016.
|
||||
* Utility class to get {@link Storable} that is storing LoganSquare (Json) generated object into preferences.
|
||||
*/
|
||||
@SuppressWarnings("CPD-START")
|
||||
//CPD: it is same code as in GoogleJsonPreferences
|
||||
public final class LoganSquarePreferences {
|
||||
|
||||
@NonNull
|
||||
public static <T> Storable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> NonNullStorable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final T defaultValue) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Storable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonListItemClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass))
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> NonNullStorable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonListItemClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final List<T> defaultValue) {
|
||||
return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass))
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
private LoganSquarePreferences() {
|
||||
}
|
||||
|
||||
public static class JsonConverter<TJsonObject> implements Converter<TJsonObject, String> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final TJsonObject object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LoganSquare.serialize(object);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public TJsonObject toObject(@NonNull final Type jsonObjectClass, @NonNull final Type storeObjectType, @Nullable final String storeValue) {
|
||||
if (storeValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LoganSquare.parse(storeValue, (Class<TJsonObject>) jsonObjectClass);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JsonListConverter<T> implements Converter<List<T>, String> {
|
||||
|
||||
@NonNull
|
||||
private final Class<T> itemClass;
|
||||
|
||||
public JsonListConverter(@NonNull final Class<T> itemClass) {
|
||||
this.itemClass = itemClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final List<T> object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LoganSquare.serialize(object, itemClass);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<T> toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) {
|
||||
if (storeValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LoganSquare.parseList(storeValue, itemClass);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.retrofit;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import retrofit2.Converter;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/02/2017.
|
||||
* Object to serialize bodies of remote requests for Retrofit.
|
||||
*
|
||||
* @param <T> Type of body object.
|
||||
*/
|
||||
public abstract class JsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
|
||||
|
||||
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RequestBody convert(@NonNull final T value) throws IOException {
|
||||
if (value instanceof ApiModel) {
|
||||
((ApiModel) value).validate();
|
||||
}
|
||||
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
writeValueToByteArray(value, byteArrayOutputStream);
|
||||
return RequestBody.create(MEDIA_TYPE, byteArrayOutputStream.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializing value to byte stream.
|
||||
*
|
||||
* @param value Value to serialize;
|
||||
* @param byteArrayOutputStream Byte stream to write serialized bytes;
|
||||
* @throws IOException Throws on serialization.
|
||||
*/
|
||||
protected abstract void writeValueToByteArray(@NonNull T value, @NonNull ByteArrayOutputStream byteArrayOutputStream) throws IOException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.retrofit;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.internal.http2.StreamResetException;
|
||||
import retrofit2.Converter;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/02/2017.
|
||||
* Object to deserialize responses of remote requests from Retrofit.
|
||||
*
|
||||
* @param <T> Type of response object.
|
||||
*/
|
||||
public abstract class JsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
|
||||
|
||||
@SuppressWarnings("PMD.AvoidInstanceofChecksInCatchClause")
|
||||
//AvoidInstanceofChecksInCatchClause: we just don't need assertion on specific exceptions
|
||||
@NonNull
|
||||
@Override
|
||||
public T convert(@NonNull final ResponseBody value) throws IOException {
|
||||
final T result;
|
||||
try {
|
||||
result = parseResponse(value);
|
||||
} catch (final IOException exception) {
|
||||
if (!(exception instanceof SocketException)
|
||||
&& !(exception instanceof InterruptedIOException)
|
||||
&& !(exception instanceof SSLException)
|
||||
&& !(exception instanceof StreamResetException)) {
|
||||
Lc.assertion(exception);
|
||||
}
|
||||
throw exception;
|
||||
} finally {
|
||||
value.close();
|
||||
}
|
||||
|
||||
if (result instanceof ApiModel) {
|
||||
validateModel((ApiModel) result);
|
||||
}
|
||||
if (result instanceof Collection) {
|
||||
validateCollection((Collection) result);
|
||||
}
|
||||
if (result instanceof Map) {
|
||||
validateCollection(((Map) result).values());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void validateModel(@NonNull final ApiModel result) throws IOException {
|
||||
try {
|
||||
result.validate();
|
||||
} catch (final ApiModel.ValidationException validationException) {
|
||||
Lc.assertion(validationException);
|
||||
throw validationException;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCollection(@NonNull final Collection result) throws IOException {
|
||||
try {
|
||||
ApiModel.validateCollection(result, getValidateCollectionRule());
|
||||
} catch (final ApiModel.ValidationException validationException) {
|
||||
Lc.assertion(validationException);
|
||||
throw validationException;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ApiModel.CollectionValidationRule getValidateCollectionRule() {
|
||||
return ApiModel.CollectionValidationRule.EXCEPTION_IF_ANY_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses response to specific object.
|
||||
*
|
||||
* @param value Response to parse;
|
||||
* @return Parsed object;
|
||||
* @throws IOException Throws during parsing.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract T parseResponse(@NonNull ResponseBody value) throws IOException;
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.2.60'
|
||||
ext.kotlin_version = '1.2.61'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ task clean(type: Delete) {
|
|||
ext {
|
||||
versions = [
|
||||
compileSdk : 27,
|
||||
minSdk : 19,
|
||||
minSdk : 16,
|
||||
supportLibrary: '27.1.1',
|
||||
navigation : '1.0.0-alpha04',
|
||||
lifecycle : '1.1.1',
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":navigation")
|
||||
|
||||
compileOnly "javax.inject:javax.inject:1"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
|
||||
implementation "android.arch.lifecycle:extensions:$versions.lifecycle"
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.lifecycle"/>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.touchin.templates.livedata
|
||||
|
||||
import android.arch.lifecycle.LifecycleOwner
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import android.arch.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
|
||||
private val pending = AtomicBoolean(false)
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<T?>) {
|
||||
super.observe(owner, Observer { value ->
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
observer.onChanged(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun setValue(value: T) {
|
||||
pending.set(true)
|
||||
super.setValue(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.arch.lifecycle.LifecycleOwner
|
||||
import android.arch.lifecycle.ViewModelProvider
|
||||
import android.arch.lifecycle.ViewModelProviders
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
|
||||
|
||||
object LifecycleViewModelProviders {
|
||||
|
||||
/**
|
||||
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
|
||||
* {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
|
||||
* <p>
|
||||
* It uses the given {@link Factory} to instantiate new ViewModels.
|
||||
*
|
||||
* @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
|
||||
* @param factory a {@code Factory} to instantiate new ViewModels
|
||||
* @return a ViewModelProvider instance
|
||||
*/
|
||||
fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider =
|
||||
when (lifecycleOwner) {
|
||||
is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
|
||||
is Fragment -> ViewModelProviders.of(lifecycleOwner, factory)
|
||||
is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory)
|
||||
else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ViewModelProvider.Factory instance from current lifecycleOwner.
|
||||
* Search #ViewModelFactoryProvider are produced according to priorities:
|
||||
* 1. View controller;
|
||||
* 2. Fragment;
|
||||
* 3. Parent fragment recursively;
|
||||
* 4. Activity;
|
||||
* 5. Application.
|
||||
*/
|
||||
fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
|
||||
when (provider) {
|
||||
is ViewModelFactoryProvider -> provider.viewModelFactory
|
||||
is ViewController<*, *> -> getViewModelFactory(provider.fragment)
|
||||
is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
|
||||
is Activity -> getViewModelFactory(provider.application)
|
||||
else -> throw IllegalArgumentException("View model factory not found.")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import android.arch.lifecycle.ViewModelProvider
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class ViewModelFactory @Inject constructor(
|
||||
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T
|
||||
= (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
interface ViewModelFactoryProvider {
|
||||
|
||||
val viewModelFactory: ViewModelFactory
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":utils")
|
||||
api project(":logging")
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
|
||||
implementation "android.arch.lifecycle:extensions:$versions.lifecycle"
|
||||
|
||||
implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.lifecycle"/>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ru.touchin.roboswag.components.utils.destroyable
|
||||
package ru.touchin.livedata.destroyable
|
||||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ru.touchin.roboswag.components.utils.destroyable
|
||||
package ru.touchin.livedata.destroyable
|
||||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package ru.touchin.livedata.dispatcher
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import ru.touchin.livedata.destroyable.BaseDestroyable
|
||||
import ru.touchin.livedata.destroyable.Destroyable
|
||||
import ru.touchin.livedata.event.CompletableEvent
|
||||
import ru.touchin.livedata.event.MaybeEvent
|
||||
import ru.touchin.livedata.event.ObservableEvent
|
||||
import ru.touchin.livedata.event.SingleEvent
|
||||
|
||||
class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable {
|
||||
|
||||
override fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable {
|
||||
liveData.value = ObservableEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = ObservableEvent.Success(data) },
|
||||
{ throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = ObservableEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable {
|
||||
liveData.value = ObservableEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = ObservableEvent.Success(data) },
|
||||
{ throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = ObservableEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): Disposable {
|
||||
liveData.value = SingleEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = SingleEvent.Success(data) },
|
||||
{ throwable -> liveData.value = SingleEvent.Error(throwable, liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun Completable.dispatchTo(liveData: MutableLiveData<CompletableEvent>): Disposable {
|
||||
liveData.value = CompletableEvent.Loading
|
||||
return untilDestroy(
|
||||
{ liveData.value = CompletableEvent.Completed },
|
||||
{ throwable -> liveData.value = CompletableEvent.Error(throwable) })
|
||||
}
|
||||
|
||||
override fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): Disposable {
|
||||
liveData.value = MaybeEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = MaybeEvent.Success(data) },
|
||||
{ throwable -> liveData.value = MaybeEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = MaybeEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.touchin.livedata.dispatcher
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import ru.touchin.livedata.event.CompletableEvent
|
||||
import ru.touchin.livedata.event.MaybeEvent
|
||||
import ru.touchin.livedata.event.ObservableEvent
|
||||
import ru.touchin.livedata.event.SingleEvent
|
||||
|
||||
interface LiveDataDispatcher {
|
||||
|
||||
fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): Disposable
|
||||
|
||||
fun Completable.dispatchTo(liveData: MutableLiveData<CompletableEvent>): Disposable
|
||||
|
||||
fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): Disposable
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package ru.touchin.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Completable].
|
||||
*/
|
||||
sealed class CompletableEvent {
|
||||
|
||||
object Loading: CompletableEvent()
|
||||
|
||||
object Completed: CompletableEvent()
|
||||
|
||||
data class Error(val throwable: Throwable): CompletableEvent()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Maybe].
|
||||
*/
|
||||
sealed class MaybeEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): MaybeEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Observable].
|
||||
*/
|
||||
sealed class ObservableEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): ObservableEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package ru.touchin.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Single].
|
||||
*/
|
||||
sealed class SingleEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): SingleEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): SingleEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): SingleEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import android.support.annotation.CallSuper
|
||||
import ru.touchin.livedata.dispatcher.BaseLiveDataDispatcher
|
||||
import ru.touchin.livedata.dispatcher.LiveDataDispatcher
|
||||
import ru.touchin.livedata.destroyable.BaseDestroyable
|
||||
import ru.touchin.livedata.destroyable.Destroyable
|
||||
|
||||
/**
|
||||
* Base class of ViewModel with [io.reactivex.disposables.Disposable] handling.
|
||||
*/
|
||||
open class RxViewModel(
|
||||
private val destroyable: BaseDestroyable = BaseDestroyable(),
|
||||
private val liveDataDispatcher: BaseLiveDataDispatcher = BaseLiveDataDispatcher(destroyable)
|
||||
) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher {
|
||||
|
||||
@CallSuper
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
destroyable.onDestroy()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">lifecycle-rx</string>
|
||||
</resources>
|
||||
|
|
@ -4,7 +4,7 @@ android {
|
|||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 08/10/2014.
|
||||
* Base interface to listen child fragments start.
|
||||
* Usually it helps to determine that fragment have showed on screen and we can change {@link android.app.Activity}'s navigation state for example.
|
||||
*/
|
||||
public interface OnFragmentStartedListener {
|
||||
|
||||
/**
|
||||
* Calls by child fragment (added via {@link android.support.v4.app.FragmentManager}) on it'sstart.
|
||||
*
|
||||
* @param fragment Child fragment which called this method.
|
||||
*/
|
||||
void onFragmentStarted(@NonNull Fragment fragment);
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
|
||||
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +37,7 @@ import ru.touchin.roboswag.components.utils.UiUtils;
|
|||
* Simple realization of one-side {@link ActionBarDrawerToggle}.
|
||||
*/
|
||||
public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle
|
||||
implements FragmentManager.OnBackStackChangedListener, BaseActivity.OnBackPressedListener {
|
||||
implements FragmentManager.OnBackStackChangedListener, OnBackPressedListener {
|
||||
|
||||
@NonNull
|
||||
private final BaseActivity activity;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.ArraySet;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
|
@ -96,13 +95,9 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) {
|
||||
|
|
@ -123,18 +118,4 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface to be implemented for someone who want to intercept device back button pressing event.
|
||||
*/
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
/**
|
||||
* Calls when user presses device back button.
|
||||
*
|
||||
* @return True if it is processed by this object.
|
||||
*/
|
||||
boolean onBackPressed();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.roboswag.components.navigation.activities;
|
||||
|
||||
/**
|
||||
* Interface to be implemented for someone who want to intercept device back button pressing event.
|
||||
*/
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
/**
|
||||
* Calls when user presses device back button.
|
||||
*
|
||||
* @return True if it is processed by this object.
|
||||
*/
|
||||
boolean onBackPressed();
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
package ru.touchin.roboswag.components.navigation.fragments;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.arch.lifecycle.Lifecycle;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
|
|
@ -27,8 +29,10 @@ import android.os.Bundle;
|
|||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
|
@ -41,6 +45,7 @@ import android.widget.FrameLayout;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.BuildConfig;
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
|
@ -54,24 +59,15 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
|||
* @param <TActivity> Type of {@link FragmentActivity} where fragment could be attached to.
|
||||
*/
|
||||
@SuppressWarnings("PMD.TooManyMethods")
|
||||
public class ViewControllerFragment<TActivity extends FragmentActivity, TState extends Parcelable> extends ViewFragment<TActivity> {
|
||||
public class ViewControllerFragment<TActivity extends FragmentActivity, TState extends Parcelable> extends Fragment {
|
||||
|
||||
private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA";
|
||||
private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA";
|
||||
|
||||
private static boolean inDebugMode;
|
||||
private static long acceptableUiCalculationTime = 100;
|
||||
|
||||
/**
|
||||
* Enables debugging features like serialization of {@link #getState()} every creation.
|
||||
*/
|
||||
public static void setInDebugMode() {
|
||||
inDebugMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets acceptable UI calculation time so there will be warnings in logs if ViewController's inflate/layout actions will take more than that time.
|
||||
* Works only if {@link #setInDebugMode()} called.
|
||||
* It's 100ms by default.
|
||||
*/
|
||||
public static void setAcceptableUiCalculationTime(final long acceptableUiCalculationTime) {
|
||||
|
|
@ -113,6 +109,8 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
@Nullable
|
||||
private ActivityResult pendingActivityResult;
|
||||
|
||||
private boolean appeared;
|
||||
|
||||
/**
|
||||
* Returns specific {@link Parcelable} which contains state of fragment and it's {@link ViewController}.
|
||||
*
|
||||
|
|
@ -128,19 +126,19 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
return viewControllerClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setHasOptionsMenu(!isChildFragment());
|
||||
|
||||
//noinspection unchecked
|
||||
viewControllerClass = (Class<ViewController<TActivity, TState>>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA);
|
||||
state = savedInstanceState != null
|
||||
? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA)
|
||||
: (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null);
|
||||
if (state != null) {
|
||||
if (inDebugMode) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
state = reserialize(state);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -148,43 +146,6 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ViewController createViewController(
|
||||
@NonNull final FragmentActivity activity,
|
||||
@NonNull final PlaceholderView view,
|
||||
@Nullable final Bundle savedInstanceState
|
||||
) {
|
||||
if (viewControllerClass.getConstructors().length != 1) {
|
||||
throw new ShouldNotHappenException("There should be single constructor for " + viewControllerClass);
|
||||
}
|
||||
final Constructor<?> constructor = viewControllerClass.getConstructors()[0];
|
||||
final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view);
|
||||
final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0;
|
||||
try {
|
||||
switch (constructor.getParameterTypes().length) {
|
||||
case 2:
|
||||
return (ViewController) constructor.newInstance(creationContext, savedInstanceState);
|
||||
case 3:
|
||||
return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState);
|
||||
default:
|
||||
throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length);
|
||||
}
|
||||
} catch (@NonNull final Exception exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
} finally {
|
||||
checkCreationTime(creationTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCreationTime(final long creationTime) {
|
||||
if (inDebugMode) {
|
||||
final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
|
||||
if (creationPeriod > acceptableUiCalculationTime) {
|
||||
LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final View onCreateView(
|
||||
|
|
@ -199,7 +160,7 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
//noinspection ConstantConditions
|
||||
viewController = createViewController(requireActivity(), (PlaceholderView) getView(), savedInstanceState);
|
||||
viewController = createViewController(requireActivity(), (ViewGroup) getView(), savedInstanceState);
|
||||
viewController.onCreate();
|
||||
if (pendingActivityResult != null) {
|
||||
viewController.onActivityResult(pendingActivityResult.requestCode, pendingActivityResult.resultCode, pendingActivityResult.data);
|
||||
|
|
@ -235,25 +196,33 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
protected void onStart(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
super.onStart(view, activity);
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (!appeared && isMenuVisible()) {
|
||||
onAppear();
|
||||
}
|
||||
if (viewController != null) {
|
||||
viewController.onStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAppear(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
super.onAppear(view, activity);
|
||||
/**
|
||||
* Called when fragment is moved in started state and it's {@link #isMenuVisible()} sets to true.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onAppear() {
|
||||
appeared = true;
|
||||
if (viewController != null) {
|
||||
viewController.onAppear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
super.onResume(view, activity);
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (viewController != null) {
|
||||
viewController.onResume();
|
||||
}
|
||||
|
|
@ -281,8 +250,8 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onPause(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
super.onPause(view, activity);
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (viewController != null) {
|
||||
viewController.onPause();
|
||||
}
|
||||
|
|
@ -297,20 +266,27 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
savedInstanceState.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisappear(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
super.onDisappear(view, activity);
|
||||
/**
|
||||
* Called when fragment is moved in stopped state or it's {@link #isMenuVisible()} sets to false.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onDisappear() {
|
||||
appeared = false;
|
||||
if (viewController != null) {
|
||||
viewController.onDisappear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
public void onStop() {
|
||||
if (appeared) {
|
||||
onDisappear();
|
||||
}
|
||||
if (viewController != null) {
|
||||
viewController.onStop();
|
||||
}
|
||||
super.onStop(view, activity);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -339,6 +315,66 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(final boolean menuVisible) {
|
||||
super.setMenuVisibility(menuVisible);
|
||||
if (getActivity() != null && getView() != null) {
|
||||
final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
|
||||
if (!appeared && menuVisible && started) {
|
||||
onAppear();
|
||||
}
|
||||
if (appeared && (!menuVisible || !started)) {
|
||||
onDisappear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if fragment have parent fragment.
|
||||
*
|
||||
* @return Returns true if fragment is in some fragment's children stack.
|
||||
*/
|
||||
public boolean isChildFragment() {
|
||||
return getParentFragment() != null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ViewController createViewController(
|
||||
@NonNull final FragmentActivity activity,
|
||||
@NonNull final ViewGroup view,
|
||||
@Nullable final Bundle savedInstanceState
|
||||
) {
|
||||
if (viewControllerClass.getConstructors().length != 1) {
|
||||
throw new ShouldNotHappenException("There should be single constructor for " + viewControllerClass);
|
||||
}
|
||||
final Constructor<?> constructor = viewControllerClass.getConstructors()[0];
|
||||
final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view);
|
||||
final long creationTime = BuildConfig.DEBUG ? SystemClock.elapsedRealtime() : 0;
|
||||
try {
|
||||
switch (constructor.getParameterTypes().length) {
|
||||
case 2:
|
||||
return (ViewController) constructor.newInstance(creationContext, savedInstanceState);
|
||||
case 3:
|
||||
return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState);
|
||||
default:
|
||||
throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length);
|
||||
}
|
||||
} catch (@NonNull final Exception exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
} finally {
|
||||
checkCreationTime(creationTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCreationTime(final long creationTime) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
|
||||
if (creationPeriod > acceptableUiCalculationTime) {
|
||||
LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PlaceholderView extends FrameLayout {
|
||||
|
||||
@NonNull
|
||||
|
|
@ -353,7 +389,7 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (inDebugMode && lastMeasureTime == 0) {
|
||||
if (BuildConfig.DEBUG && lastMeasureTime == 0) {
|
||||
lastMeasureTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +397,7 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
@Override
|
||||
protected void onDraw(@NonNull final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (inDebugMode && lastMeasureTime > 0) {
|
||||
if (BuildConfig.DEBUG && lastMeasureTime > 0) {
|
||||
final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime;
|
||||
if (layoutTime > acceptableUiCalculationTime) {
|
||||
LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime);
|
||||
|
|
|
|||
|
|
@ -1,229 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation.fragments;
|
||||
|
||||
import android.arch.lifecycle.Lifecycle;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.utils.BiConsumer;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Non-background fragment that have specific activity as a parent.
|
||||
*
|
||||
* @param <TActivity> Type of activity which to such fragment could be attached.
|
||||
*/
|
||||
public abstract class ViewFragment<TActivity extends FragmentActivity> extends Fragment implements OnFragmentStartedListener {
|
||||
|
||||
private boolean appeared;
|
||||
|
||||
/**
|
||||
* Returns if fragment have parent fragment.
|
||||
*
|
||||
* @return Returns true if fragment is in some fragment's children stack.
|
||||
*/
|
||||
public boolean isChildFragment() {
|
||||
return getParentFragment() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns specific activity which to this fragment could be attached.
|
||||
*
|
||||
* @return Returns parent activity.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
protected final TActivity getBaseActivity() {
|
||||
if (getActivity() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return (TActivity) getActivity();
|
||||
} catch (final ClassCastException exception) {
|
||||
Lc.assertion(exception);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
throw new IllegalStateException("Method onCreateView() should be overridden");
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onFragmentStarted(@NonNull final Fragment fragment) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
if (getView() == null || getBaseActivity() == null) {
|
||||
Lc.assertion("View and activity shouldn't be null");
|
||||
}
|
||||
}
|
||||
|
||||
private void callMethodAfterInstantiation(@NonNull final BiConsumer<View, TActivity> action) {
|
||||
if (getView() == null || getBaseActivity() == null) {
|
||||
Lc.assertion("View and activity shouldn't be null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
action.accept(getView(), getBaseActivity());
|
||||
} catch (final Exception exception) {
|
||||
Lc.assertion(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
callMethodAfterInstantiation(this::onStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement of {@link #onStart} with non null activity as first parameter.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
@CallSuper
|
||||
@SuppressWarnings("RestrictedApi")
|
||||
//RestrictedApi: we need isMenuVisible() to check analytics rightly!
|
||||
protected void onStart(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
if (getParentFragment() instanceof OnFragmentStartedListener) {
|
||||
((OnFragmentStartedListener) getParentFragment()).onFragmentStarted(this);
|
||||
} else if (activity instanceof OnFragmentStartedListener) {
|
||||
((OnFragmentStartedListener) activity).onFragmentStarted(this);
|
||||
}
|
||||
if (!appeared && isMenuVisible()) {
|
||||
onAppear(view, activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in started state and it's {@link #isMenuVisible()} sets to true.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
protected void onAppear(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
appeared = true;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
callMethodAfterInstantiation(this::onResume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement of {@link #onResume} with non null activity as first parameter.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onResume(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(final boolean menuVisible) {
|
||||
super.setMenuVisibility(menuVisible);
|
||||
if (getBaseActivity() != null && getView() != null) {
|
||||
final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
|
||||
if (!appeared && menuVisible && started) {
|
||||
onAppear(getView(), getBaseActivity());
|
||||
}
|
||||
if (appeared && (!menuVisible || !started)) {
|
||||
onDisappear(getView(), getBaseActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void onPause() {
|
||||
callMethodAfterInstantiation(this::onPause);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement of {@link #onPause} with non null activity as first parameter.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onPause(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in stopped state or it's {@link #isMenuVisible()} sets to false.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
protected void onDisappear(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
appeared = false;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void onStop() {
|
||||
callMethodAfterInstantiation(this::onStop);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement of {@link #onStop} with non null activity as first parameter.
|
||||
*
|
||||
* @param view Instantiated view.
|
||||
* @param activity Activity which fragment attached to.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onStop(@NonNull final View view, @NonNull final TActivity activity) {
|
||||
if (appeared) {
|
||||
onDisappear(view, activity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,14 +3,16 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.touchin.roboswag.components"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.compileSdk
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
|
|
@ -21,6 +23,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
include ':sample', ':utils', ':logging', ':navigation', ':storable'
|
||||
include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ android {
|
|||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ android {
|
|||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package ru.touchin.roboswag.core.utils;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A functional interface (callback) that accepts two values (of possibly different types).
|
||||
* @param <T1> the first value type
|
||||
* @param <T2> the second value type
|
||||
*/
|
||||
public interface BiConsumer<T1, T2> {
|
||||
|
||||
/**
|
||||
* Performs an operation on the given values.
|
||||
* @param t1 the first value
|
||||
* @param t2 the second value
|
||||
* @throws Exception on error
|
||||
*/
|
||||
void accept(@Nullable T1 t1, @Nullable T2 t2) throws Exception;
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
/**
|
||||
* Utility class that is providing common methods related to android device.
|
||||
*/
|
||||
public final class DeviceUtils {
|
||||
|
||||
/**
|
||||
* Detects active network type.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return Active network type {@link NetworkType}
|
||||
*/
|
||||
@NonNull
|
||||
public static NetworkType getNetworkType(@NonNull final Context context) {
|
||||
if (context.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid())
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return NetworkType.UNKNOWN;
|
||||
}
|
||||
final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
@SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
if (info == null || !info.isConnected()) {
|
||||
return NetworkType.NONE;
|
||||
}
|
||||
if (info.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return NetworkType.WI_FI;
|
||||
}
|
||||
if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
|
||||
final int networkType = info.getSubtype();
|
||||
switch (networkType) {
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
case TelephonyManager.NETWORK_TYPE_CDMA:
|
||||
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||
case TelephonyManager.NETWORK_TYPE_IDEN:
|
||||
return NetworkType.MOBILE_2G;
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_B:
|
||||
case TelephonyManager.NETWORK_TYPE_EHRPD:
|
||||
case TelephonyManager.NETWORK_TYPE_HSPAP:
|
||||
return NetworkType.MOBILE_3G;
|
||||
case TelephonyManager.NETWORK_TYPE_LTE:
|
||||
case 19: // NETWORK_TYPE_LTE_CA is hide
|
||||
return NetworkType.MOBILE_LTE;
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return NetworkType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
return NetworkType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if some network connected.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return true if network connected, false otherwise.
|
||||
*/
|
||||
public static boolean isNetworkConnected(@NonNull final Context context) {
|
||||
return getNetworkType(context) != NetworkType.NONE;
|
||||
}
|
||||
|
||||
private DeviceUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Available network types.
|
||||
*/
|
||||
public enum NetworkType {
|
||||
/**
|
||||
* Mobile 2G network.
|
||||
*/
|
||||
MOBILE_2G("2g"),
|
||||
/**
|
||||
* Mobile 3G network.
|
||||
*/
|
||||
MOBILE_3G("3g"),
|
||||
/**
|
||||
* Mobile LTE network.
|
||||
*/
|
||||
MOBILE_LTE("lte"),
|
||||
/**
|
||||
* Wi-Fi network.
|
||||
*/
|
||||
WI_FI("Wi-Fi"),
|
||||
/**
|
||||
* Unknown network type.
|
||||
*/
|
||||
UNKNOWN("unknown"),
|
||||
/**
|
||||
* No network.
|
||||
*/
|
||||
NONE("none");
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
|
||||
NetworkType(@NonNull final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Network type readable name.
|
||||
*/
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue