I am attempting to build my own implementation of a system for localizing strings for internationalization in Flutter apps, using Flutter's LocalizationsDelegate, and loading the localized strings from JSON files (one json file for each locale).
Everything works fine, but when the app is launched, there's a lapse of some milliseconds in which the screen goes black. The reason for this is that, since I am loading the JSON files using json.decode, the way I am retrieving the localized strings is asynchronous. The app loads its MaterialApp widget and then starts parsing the JSONs for localization. That is when the app goes black until it parses the JSON successfully.
Here is my implementation of my i18n system:
class Localization extends LocaleCodeAware with LocalizationsProvider {
Localization(this.locale) : super(locale.toString());
final Locale locale;
static Localization of(BuildContext context) =>
Localizations.of<Localization>(context, Localization);
}
class AppLocalizationsDelegate extends LocalizationsDelegate<Localization> {
const AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
#override
Future<Localization> load(Locale locale) async {
final localization = Localization(locale);
await localization.load();
return localization;
}
#override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:example/resources/asset_paths.dart' as paths;
abstract class LocaleCodeAware {
LocaleCodeAware(this.localeCode);
final String localeCode;
}
mixin LocalizationsProvider on LocaleCodeAware {
static Map<String, String> _values;
Future<void> load() async {
final string = await rootBundle.loadString('${paths.i18n}$localeCode.json');
final Map<String, dynamic> jsonMap = json.decode(string);
_values = jsonMap.map((key, value) => MapEntry(key, value.toString()));
}
String get appTitle => _values['appTitle'];
}
Here is my main.dart file, with its MaterialApp widget.
import 'package:flutter/material.dart';
void main() => runApp(ExampleApp());
class ExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
localizationsDelegates: [
const AppLocalizationsDelegate(),
],
supportedLocales: const [Locale("en"), Locale("es")],
home: const AppNavigator(),
);
}
If instead of having the localized strings in JSON files, I assign a Map<String, String> to my _values map, and I load the strings directly from there, the black screen issue is gone, because the values are hardcoded and can be loaded synchronously.
So my question is, how can I have my app wait in splash screen until the localized strings are loaded from the JSON files?
Do you have any errors in your logs? The black screen could only be caused by 1. The current route not building a visible page or 2. The build() function of the current route throwing exceptions.
As for loading the localizations while on the splash screen, you can do that within your main() function:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
List<Locale> locales = WidgetsBinding.instance.window.locales;
// ... logic to decide which locale to use and load localizations for
final string = await rootBundle.loadString('${paths.i18n}$localeCode.json');
final Map<String, dynamic> jsonMap = json.decode(string);
runApp(ExampleApp(jsonMap));
}
This way, you can read the JSON file and convert it to a Map while on the splash screen, and then pass it to ExampleApp, which can in turn pass it to AppLocalizationsDelegate, which can store it as a local variable and use it within load().
checkout easy_localization package , its simpler than the most out there
Related
I have a main screen with several Buttons which push the user to different screens and I have a FAB which pushs the user to a screen he saved as favorite. I do this with a cubit from BLoC: (FAB onPress function): Navigator.push(context, MaterialPageRoute(builder: (context) => state.favWidget!));
Problem: I try to save the state of the Cubit (Shared prefs or Hydrated, dosent matter for me - I prefere HydratedCubit) so that the user reaches his favorite screen even after a restart.
Here is the FavWidgetState:
class FavWidgetState extends Equatable {
factory FavWidgetState.initial() {
return const FavWidgetState(favWidget: null);
}
final Widget? favWidget;
const FavWidgetState({
required this.favWidget,
});
Map<String, dynamic> toJson() {
return {
'favWidget': this.favWidget,
};
}
factory FavWidgetState.fromJson(Map<String, dynamic> json) {
return FavWidgetState(
favWidget: json['favWidget'] as Widget,
);
}
#override
List<Object?> get props => [favWidget];
I added the fromJson and toJson with the dart dataclass extention
Inside FaveWidgetcubit is just little code:
FavWidgetCubit() : super(FavWidgetState.initial());
void setFaveWidget({required Widget? widget})async{
emit(FavWidgetState(favWidget: widget));
}
#override
FavWidgetState? fromJson(Map<String, dynamic> json) {
return FavWidgetState.fromJson(json);
}
#override
Map<String, dynamic>? toJson(FavWidgetState state) {
return state.toJson();
}
You can imagine that this won't with the error: Unhandled Exception: Converting object to an encodable object failed: Instance of ...
How do I make the Widget? widget to a convertible object?
I have my model for json, service to get api
I just dont know how to get image like this like this
from this json from this
the Ipone Mega is the carousel slider(4 images in json link), below the other is just column
if you could show it in carousel Slider i will be very grateful to you
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_work/apiService/fetch_data.dart';
import 'package:my_work/apiService/phone.dart';
class CarouselSliderData extends StatefulWidget{
const CarouselSliderData({super.key});
#override
State<CarouselSliderData> createState() => CarouselSliderDataState();
}
class CarouselSliderDataState extends State<CarouselSliderData> {
Phone? info;
#override
void initState() {
DioService.getDataMocky(
url:'https://run.mocky.io/v3/654bd15e-b121-49ba-a588-960956b15175'
).then((value) async {
if(value != null){
setState((){
info = value!;
});
}
}).catchError(
(value) => (value),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image(image:)
],
);
}
}
Step 1: get the json from API. I call this variable is
Map<String, dynamic> json;
Step 2: As you can see, "home_store" of your json is a list of phones. Therefore, you create a model class called PhoneModel like this:
class PhoneModel {
int id;
bool isNew;
String title;
String subtitle;
String picture;
bool isBuy;
/// constructor
PhoneModel({this.id ...});
/// from json to class model
PhoneModel.fromJson(Map<String, dynamic> json) {
this.id = json["id"];
this.isNew = json["is_new"];
...
}
}
Then do like this to catch the list of items:
List<PhoneModel> listPhones = List<PhoneModel>.from(
(json['home_store'] as List).map((e) => PhoneModel.fromJson(e)),
);
Now your list of phone is variable listPhones. Url is field picture. Do Image.network or anythings else... Good luck!
Currently, I have code written in regular Java that gets a public-readable s3 object's InputStream and creates a thumbnail image.
Now I am looking to convert it to using Reactive Java using Project Reactor on Spring Webflux. The following is my code so far and I don't know how to convert it to a inpustream:
public ByteArrayOutputStream createThumbnail(String fileKey, String imageFormat) {
try {
LOG.info("fileKey: {}, endpoint: {}", fileKey, s3config.getSubdomain());
GetObjectRequest request = GetObjectRequest.builder()
.bucket(s3config.getBucket())
.key(fileKey)
.build();
Mono.fromFuture(s3client.getObject(request, new FluxResponseProvider()))
.map(fluxResponse -> new
ResponseInputStream(fluxResponse.sdkResponse, <ABORTABLE_INPUSTREAM?>))
I saw ResponseInputStream and I am thinking maybe that is the way to create a inputstream but I don't know what to put as AbortableInputStream in that constructor?
Is that even the way to create a inpustream?
Btw, I am using FluxResponseProvider from baeldung's documentation which is:
import reactor.core.publisher.Flux;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
class FluxResponseProvider implements AsyncResponseTransformer<GetObjectResponse,FluxResponse> {
private FluxResponse response;
#Override
public CompletableFuture<FluxResponse> prepare() {
response = new FluxResponse();
return response.cf;
}
#Override
public void onResponse(GetObjectResponse sdkResponse) {
this.response.sdkResponse = sdkResponse;
}
#Override
public void onStream(SdkPublisher<ByteBuffer> publisher) {
response.flux = Flux.from(publisher);
response.cf.complete(response);
}
#Override
public void exceptionOccurred(Throwable error) {
response.cf.completeExceptionally(error);
}
}
class FluxResponse {
final CompletableFuture<FluxResponse> cf = new CompletableFuture<>();
GetObjectResponse sdkResponse;
Flux<ByteBuffer> flux;
}
Any body know how to get a inpustream from s3 object in reactive java? I am using awssdk version 2.17.195.
I am having the nested json where I want parse the worksheetData and display the list of worksheetdata in separate cards. I have tried used online tool parse but when I print the data it throws an error called "type 'List' is not a subtype of type 'Map"
#Update
Below is the home.dart file where I am getting the data error
Home.dart
class WorkSheet extends StatefulWidget {
const WorkSheet({Key key}) : super(key: key);
#override
_WorkSheetState createState() => new _WorkSheetState();
}
class _WorkSheetState extends State<WorkSheet> {
Future<String> loadSheetDataFromAssets() async {
return await DefaultAssetBundle.of(context)
.loadString('assets/example.json');
}
Future loadSheetData() async {
String jsonString = await loadSheetDataFromAssets();
final jsonResponse = json.decode(jsonString);
SheetData sheetData = new SheetData.fromJson(jsonResponse);
print('PName : ${sheetData.projectName}');
print('Worksheet : ${sheetData.worksheetData}');
print(sheetData);
}
#override
void initState() {
super.initState();
loadSheetData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Work sheet data'),
),
body: FutureBuilder(
future: loadSheetData(),
builder: (context, snapshot){
if(snapshot.data == null){
return Center(child: Text(snapshot.error));
}else{
return Card(child: Text(snapshot.data.toString()));
}
}
)
);
}
}
You could use some external tools to generate your models like quicktype
Or any of the approaches described in the official documentation doc
Make sure your class classes you want to convert fromJson are annotated with #JsonSerializable(). Please follow flutter documentation for this https://flutter.dev/docs/development/data-and-backend/json
This with Autoatically convert all your nested classes that are declared with #JsonSerializable() but if you have to convert a list from Json, you have to write some extra code like this below
Map jsonObject = json.decode(jsonString);
Iterable list = json.decode(jsonObject['worksheetData']);
List<WorksheetData> datasheet = list.map((f) => WorksheetData.fromJson(f)).toList();
I'm exploring reactive programming with Spring Webflux and therefore, I'm trying to make my code completely nonblocking to get all the benefits of a reactive application.
Currently my code for the method to parse a Json String to a JsonNode to get specific values (in this case the elementId) looks like this:
public Mono<String> readElementIdFromJsonString(String jsonString){
final JsonNode jsonNode;
try {
jsonNode = MAPPER.readTree(jsonString);
} catch (IOException e) {
return Mono.error(e);
}
final String elementId = jsonNode.get("elementId").asText();
return Mono.just(elementId);
}
However, IntelliJ notifies me that I'm using an inappropriate blocking method call with this code:
MAPPER.readTree(jsonString);
How can I implement this code in a nonblocking way? I have seen that since Jackson 2.9+, it is possible to parse a Json String in a nonblocking async way, but I don't know how to use that API and I couldn't find an example how to do it correctly.
I am not sure why it is saying it is a blocking call since Jackson is non blocking as far as I know. Anyway one way to resolve this issue is to use schedulers if you do not want to use any other library. Like this.
public Mono<String> readElementIdFromJsonString(String input) {
return Mono.just(Mapper.readTree(input))
.map(it -> it.get("elementId").asText())
.onErrorResume( it -> Mono.error(it))
.subscribeOn(Schedulers.boundedElastic());
}
Something along that line.
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.codec.json.AbstractJackson2Decoder;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
#FunctionalInterface
public interface MessageParser<T> {
Mono<T> parse(String message);
}
public class JsonNodeParser extends AbstractJackson2Decoder implements MessageParser<JsonNode> {
private static final MimeType MIME_TYPE = MimeTypeUtils.APPLICATION_JSON;
private static final ObjectMapper OBJECT_MAPPER = allocateDefaultObjectMapper();
private final DefaultDataBufferFactory factory;
private final ResolvableType resolvableType;
public JsonNodeParser(final Environment env) {
super(OBJECT_MAPPER, MIME_TYPE);
this.factory = new DefaultDataBufferFactory();
this.resolvableType = ResolvableType.forClass(JsonNode.class);
this.setMaxInMemorySize(100000); // 1MB
canDecodeJsonNode();
}
#Override
public Mono<JsonNode> parse(final String message) {
final byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
return decode(bytes);
}
private Mono<JsonNode> decode(final byte[] bytes) {
final DefaultDataBuffer defaultDataBuffer = this.factory.wrap(bytes);
return this.decodeToMono(Mono.just(defaultDataBuffer), this.resolvableType, MIME_TYPE, Map.of())
.ofType(JsonNode.class)
.subscribeOn(Schedulers.boundedElastic())
.doFinally((t) -> DataBufferUtils.release(defaultDataBuffer));
}
private void canDecodeJsonNode() {
if (!canDecode(this.resolvableType, MIME_TYPE)) {
throw new IllegalStateException(String.format("JsonNodeParser doesn't supports the given tar`enter code here`get " +
"element type [%s] and the MIME type [%s]", this.resolvableType, MIME_TYPE));
}
}
}